PHPackages                             percipiolondon/craft-timeloop - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. percipiolondon/craft-timeloop

Abandoned → [craftpulse/craft-timeloop](/?search=craftpulse%2Fcraft-timeloop)Craft-plugin[Utility &amp; Helpers](/categories/utility)

percipiolondon/craft-timeloop
=============================

This Craft plugin will generate an array of dates between a start and end date based on frequency.

4.1.1(3y ago)56611[10 issues](https://github.com/percipioglobal/craft-timeloop/issues)[7 PRs](https://github.com/percipioglobal/craft-timeloop/pulls)proprietaryPHPPHP ^8.0.2CI passing

Since Apr 22Pushed 2w ago1 watchersCompare

[ Source](https://github.com/percipioglobal/craft-timeloop)[ Packagist](https://packagist.org/packages/percipiolondon/craft-timeloop)[ RSS](/packages/percipiolondon-craft-timeloop/feed)WikiDiscussions develop-v5 Synced 1w ago

READMEChangelog (10)Dependencies (5)Versions (24)Used By (0)

Timeloop for Craft CMS
======================

[](#timeloop-for-craft-cms)

Timeloop generates arrays of dates between a start and end date based on a frequency: recurring events, repeating classes, courses, payment schedules, all without complex recurrence inputs. Authors pick a start date, a frequency, and an interval; your templates get clean `DateTime` arrays back.

[![Screenshot](./resources/img/timeloop-banner.jpg)](./resources/img/timeloop-banner.jpg)

Features
--------

[](#features)

- **Timeloop field type**: a single field that captures start/end dates, optional start/end times, frequency, interval, and refinements per frequency.
- **Four frequencies**: daily, weekly, monthly, and yearly, each with a configurable interval (every 2 weeks, every 3 months, ...).
- **Weekly day selection**: repeat on specific weekdays, e.g. every week on Monday and Friday.
- **Monthly ordinals**: repeat on the first, second, third, fourth, or last weekday of the month, e.g. "last Saturday of every month".
- **Computed dates in Twig**: `dates`, `upcoming`, and `nextUpcoming` on the field value, plus a `recurringDates()` function to collect dates across a whole element query.
- **Reminder offset**: store a reminder period (e.g. 2 days before) and read the computed reminder date for the next occurrence.
- **GraphQL support**: query the field's raw settings and computed dates, and set the field through mutations.

Requirements
------------

[](#requirements)

- Craft CMS 5.0 or newer
- PHP 8.2+

Installation
------------

[](#installation)

### Standard Craft CMS Installation

[](#standard-craft-cms-installation)

1. Open your terminal and navigate to your Craft project: ```
    cd /path/to/project
    ```
2. Install the plugin via Composer: ```
    composer require craftpulse/craft-timeloop
    ```
3. Install the plugin: ```
    ./craft plugin/install timeloop
    ```

    Alternatively, activate it via **Settings → Plugins** in the Craft Control Panel.

### Installing on DDEV

[](#installing-on-ddev)

1. Install the Timeloop plugin: ```
    ddev composer require craftpulse/craft-timeloop
    ```
2. Install the plugin in Craft CMS: ```
    ddev craft plugin/install timeloop
    ```

Or install through the Plugin Store: **Settings → Plugins → Search "Timeloop"**.

Setting up the field
--------------------

[](#setting-up-the-field)

Create a new field and pick **Timeloop** as the field type. The field has one setting:

- **Show Times**: when enabled, authors can set a start time and end time alongside the start and end dates. The times are merged into the stored dates (occurrences start at the start time; the loop end date ends at the end time, or 23:59 when no end time is set).

Authors then configure each entry's loop:

- **Start date** (required): the first occurrence.
- **End date** (optional): when the loop stops. Without an end date, dates are generated up to 20 years ahead.
- **Loop period**: the frequency (daily, weekly, monthly, or yearly) and the interval between occurrences. Weekly loops can target specific days of the week; monthly loops can target an ordinal weekday (e.g. "last Friday").
- **Reminder** (optional): a value and unit (days, weeks, months, or years) subtracted from the next occurrence to produce a reminder date. Timeloop computes the date; sending the actual notification is up to your project.

Templating
----------

[](#templating)

The examples below assume an `events` section with a Timeloop field whose handle is `schedule`.

### Getting the computed dates

[](#getting-the-computed-dates)

`dates` returns the upcoming occurrences as an array of `DateTime` objects (future dates only, capped at 100 by default):

```
{% for date in entry.schedule.dates %}
    {{ date | date('d/m/Y H:i') }}
{% endfor %}
```

Use `getDates()` to control the limit and whether past dates are included:

```
{# The next 5 occurrences #}
{% for date in entry.schedule.getDates(5) %}
    {{ date | date('d/m/Y H:i') }}
{% endfor %}

{# Occurrences from the start date onwards, including past ones (max 100) #}
{% for date in entry.schedule.getDates(0, false) %}
    {{ date | date('d/m/Y H:i') }}
{% endfor %}
```

The generated dates take all field values into account: frequency, interval, weekly days, and monthly ordinals.

### Upcoming occurrences

[](#upcoming-occurrences)

```
{# The first upcoming occurrence #}
{{ entry.schedule.upcoming | date('d/m/Y H:i') }}

{# The occurrence after that #}
{{ entry.schedule.nextUpcoming | date('d/m/Y H:i') }}
```

Both return `null` when the loop has no (more) upcoming dates, so guard accordingly:

```
{% if entry.schedule.upcoming %}
    Next class: {{ entry.schedule.upcoming | date('l d F Y') }}
{% endif %}
```

### The entered dates and times

[](#the-entered-dates-and-times)

The raw field values are available as `DateTime` objects (or `null` when not set):

```
{{ entry.schedule.loopStartDate | date('Y-m-d\\TH:i:sP') }}   {# includes the start time #}
{{ entry.schedule.loopEndDate | date('Y-m-d\\TH:i:sP') }}     {# includes the end time #}
{{ entry.schedule.loopStartTime | date('H:i') }}
{{ entry.schedule.loopEndTime | date('H:i') }}
```

### The loop period

[](#the-loop-period)

`period` returns the recurrence configuration:

```
{{ entry.schedule.period.frequency }}   {# ISO 8601 duration: P1D, P1W, P1M or P1Y #}
{{ entry.schedule.period.cycle }}       {# the interval, e.g. 2 for "every 2 weeks" #}

{# Selected weekdays for weekly loops #}
{% for day in entry.schedule.period.days %}
    {{ day }}
{% endfor %}
```

For monthly loops, `timestring` returns the ordinal weekday configuration:

```
{% set timestring = entry.schedule.timestring %}
{% if timestring and timestring.ordinal != 'none' %}
    Repeats every {{ timestring.ordinal }} {{ timestring.day }} of the month
{% endif %}
```

`timestring` is `null` when no timestring data is stored, and `ordinal`/`day` are the string `'none'` when no selection has been made.

### The reminder date

[](#the-reminder-date)

`reminder` returns the reminder date for the first upcoming occurrence, or `null` when no reminder is configured:

```
{% if entry.schedule.reminder %}
    Send a reminder on {{ entry.schedule.reminder | date('d/m/Y') }}
{% endif %}
```

### Collecting dates across entries

[](#collecting-dates-across-entries)

The `recurringDates()` Twig function expands a whole element query into recurring dates within a window, handy for calendars and agenda views. Pass the query, the Timeloop field handle, and a start and end date (boundaries are inclusive):

```
{% set agenda = recurringDates(craft.entries.section('events'), 'schedule', '2026-01-01', '2026-12-31') %}

{% for item in agenda %}
    {{ item.entryTitle }}
    {% for date in item.dates %}
        {{ date | date('d/m/y H:i') }}
    {% endfor %}
{% endfor %}
```

Each item contains `entryId`, `entryTitle`, and `dates` (an array of `DateTime` objects within the window, including past dates).

GraphQL
-------

[](#graphql)

### Querying

[](#querying)

The field exposes the stored settings and the computed dates. Dates support Craft's `@formatDateTime` directive; `loopStartTime` and `loopEndTime` resolve to `H:i` strings.

```
{
  entries(section: "events") {
    title
    ... on event_Entry {
      schedule {
        loopStartDate
        loopEndDate
        loopStartTime
        loopEndTime
        loopPeriod {
          frequency
          cycle
          days
          timestring {
            ordinal
            day
          }
        }
        getDates(limit: 5) @formatDateTime(format: "d/m/Y")
        getUpcoming
        getReminder
      }
    }
  }
}
```

- `loopPeriod.frequency`: the selected frequency (`P1D`, `P1W`, `P1M` or `P1Y`)
- `loopPeriod.cycle`: the interval between occurrences
- `loopPeriod.days`: the selected weekdays for weekly loops
- `loopPeriod.timestring`: the `ordinal` (e.g. `last`) and `day` (e.g. `saturday`) for monthly loops
- `getDates`: the computed dates; accepts `limit` (default `100`) and `futureDates` (default `true`)
- `getUpcoming`: the first upcoming occurrence
- `getReminder`: the reminder date for the first upcoming occurrence

### Mutating

[](#mutating)

The field can be set through entry mutations. The input type accepts `loopStartDate`, `loopEndDate`, `loopStartTime`, `loopEndTime`, and a `loopPeriod` object:

```
mutation {
  save_events_event_Entry(
    title: "Weekly yoga class"
    schedule: {
      loopStartDate: "2026-09-01"
      loopEndDate: "2027-06-30"
      loopPeriod: {
        frequency: "P1W"
        cycle: 1
        days: ["Monday", "Thursday"]
      }
    }
  ) {
    id
  }
}
```

For monthly loops, pass a `timestring` object with `ordinal` (`First`, `Second`, `Third`, `Fourth`, `Last`) and `day` (e.g. `Saturday`) inside `loopPeriod`. Reminder settings cannot be set through GraphQL.

Support
-------

[](#support)

- **Plugin Store**: [plugins.craftcms.com/timeloop](https://plugins.craftcms.com/timeloop)
- **Bugs and feature requests**: [github.com/craftpulse/craft-timeloop/issues](https://github.com/craftpulse/craft-timeloop/issues)
- **Email**:

Brought to you by [CraftPulse](https://craft-pulse.com/).

###  Health Score

39

—

LowBetter than 84% of packages

Maintenance43

Moderate activity, may be stable

Popularity20

Limited adoption so far

Community11

Small or concentrated contributor base

Maturity68

Established project with proven stability

 Bus Factor1

Top contributor holds 85.9% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~37 days

Recently: every ~76 days

Total

19

Last Release

1257d ago

Major Versions

0.1.0 → 1.0.0-rc2021-05-06

v1.x-dev → 4.0.0-beta.12022-05-02

### Community

Maintainers

![](https://www.gravatar.com/avatar/9d008342ae63e14503bcee1a019015893578554e4d2cc567a41d87725d870c92?d=identicon)[percipio](/maintainers/percipio)

---

Top Contributors

[![michtio](https://avatars.githubusercontent.com/u/5818021?v=4)](https://github.com/michtio "michtio (134 commits)")[![cookie10codes](https://avatars.githubusercontent.com/u/20947573?v=4)](https://github.com/cookie10codes "cookie10codes (20 commits)")[![EmilyRackliffe](https://avatars.githubusercontent.com/u/47784491?v=4)](https://github.com/EmilyRackliffe "EmilyRackliffe (2 commits)")

---

Tags

cmsCraftcraftcmscraft-plugintimeloop

###  Code Quality

TestsCodeception

### Embed Badge

![Health badge](/badges/percipiolondon-craft-timeloop/health.svg)

```
[![Health](https://phpackages.com/badges/percipiolondon-craft-timeloop/health.svg)](https://phpackages.com/packages/percipiolondon-craft-timeloop)
```

###  Alternatives

[verbb/formie

The most user-friendly forms plugin for Craft.

101400.6k78](/packages/verbb-formie)[verbb/hyper

A user-friendly links field for Craft.

24153.5k14](/packages/verbb-hyper)[verbb/vizy

A flexible visual editor field for Craft.

4251.5k1](/packages/verbb-vizy)[verbb/icon-picker

A slick field to pick icons from. Supports SVGs, Sprites, Webfonts, Font Awesome and more.

16172.7k7](/packages/verbb-icon-picker)[nystudio107/craft-seomatic

SEOmatic facilitates modern SEO best practices &amp; implementation for Craft CMS 5. It is a turnkey SEO system that is comprehensive, powerful, and flexible.

1741.5M73](/packages/nystudio107-craft-seomatic)[verbb/comments

Add comments to your site.

13754.0k1](/packages/verbb-comments)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
