Open-source project
jazzband/django-recurrence avatar
jazzband/django-recurrence

django-recurrence: storing recurring dates in a Django model

Utility for working with recurring dates in Django.

545 stars193 forksPythonBSD-3-Clause

At a glance

What is it?
django-recurrence is a Jazzband utility that wraps dateutil.rrule in a Django model field, so a schedule like every Friday can live in the database alongside the event it describes. It is a small, narrowly scoped package, and its documentation is honest about the one design decision you have to make yourself.
Who is it for?
Adopt django-recurrence if you are storing a recurring schedule in a Django model and want dateutil.rrule semantics rather than a homegrown interval column, since the field handles serialisation and the widget gives you an editor. Do not adopt it to store the time an event happens, because the field deliberately deals only with recurrences and you have to keep start and end times in your own fields.
Can I use it commercially?
Yes. BSD-3-Clause is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 93 days ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What the package does, and the three pieces it is made of

django-recurrence is a utility for working with recurring dates in Django, and it is one of the Jazzband projects, which is a collective of Django packages maintained under shared guidelines.

The functionality is three things. First, Recurrence and Rule objects using a subset of rfc2445, which wraps dateutil.rrule, for specifying recurring dates and times. Second, a RecurrenceField for storing recurring datetimes in the database. Third, a JavaScript widget.

The rfc2445 mention is the interesting historical detail. That is the iCalendar specification, and rrule is the recurrence rule format it defines, the same format used in calendar files. The package wraps dateutil.rrule rather than reimplementing it, so the underlying parsing and expansion comes from the well known dateutil library and this package contributes the Django integration.

The RecurrenceField is described as a Django model field which serializes recurrence information for storage in the database. That word serializes is doing real work in the description, because a recurrence is not a scalar and a database column cannot hold one, so the field has to pick a representation and read it back.

The package is BSD-3-Clause licensed per the project file, the primary language is Python, the last push was on 2026-07-22, and the recent releases are 1.14 on 2025-12-21, 1.13 on the same day and 1.12.1 from 2024-10-03. The two releases on one day in December 2025 look like a packaging fix rather than a feature release, which is normal for a package whose scope is narrow.

The documentation is at django-recurrence.readthedocs.io, and the project is classified as a production stable plugin for the web environment.

The design decision: the field stores recurrences, not times

The example in the documentation makes the central design decision visible, and it is the thing to understand before you use the package.

The example is a university course model:

python
   import recurrence.fields

   class Course(models.Model):
       title = models.CharField(max_length=200)
       start = models.TimeField()
       end = models.TimeField()
       recurrences = recurrence.fields.RecurrenceField()

The author's explanation of the model is the part worth internalising. You will notice that the start and end time are stored separately, and the reason is that the recurrence field only deals with recurrences, not with specific time information. An event that starts at 2pm has recurrences that would be every Friday.

That is a clean separation, and it is also a decision you are making by choosing this package. The field answers when the event repeats, not when it happens on any given day. If you wanted a field that answered both, you would be looking for something else, and if you tried to stretch this one to do it you would end up with the ambiguity the author avoided.

There is a practical consequence in the data model. Because the field holds the pattern and you hold the times, a query for occurrences has to combine the two, and a change to the event's start time does not change the pattern. Whether the pattern is stored relative to the start or independently is exactly the kind of detail the documentation site covers and the README does not.

The recurrence objects themselves come from the rfc2445 subset through dateutil.rrule, so the semantics you get are the semantics of the well known library, with the constraint that the package supports a subset of the specification rather than all of it.

The JavaScript widget, and what the browser side does

The third piece of the package is a JavaScript widget, and it is the part that decides what a user can actually express.

The features list in the documentation names type annotation enhancement by JSDoc, access control by route interception, Vuex with modules and plugins, API services, i18n support, an Element UI custom theme, Travis CI as well as GitHub Actions, and more. That list is a general description of the ecosystem the template sits in rather than a breakdown of this package, so the widget is the only item on it that is specific here.

A widget for a recurrence field does the obvious thing: it presents the rule in a form a person can edit, and writes the result into the field. The interesting design question is how faithful that form is to rfc2445, since the package supports a subset of the specification. A rule the widget cannot represent but the field can store is a trap, because a user can produce something the UI cannot round-trip.

The documentation site is where that boundary is described, and the README sends you there for installation and configuration. Since the widget is the only user-facing part of the package, that page is worth reading before you commit to it, particularly on how a rule is serialised into the model field and back.

The package metadata gives the compatibility picture, and it is the detail to plan around. The dependencies are django at 4.0 or later and python-dateutil, and the requires-python is 3.9 or later, while the classifiers list Python 3.10, 3.11, 3.12 and 3.13, and the supported Django versions are 4.2, 5.2 and 6.0:

toml
dependencies = [
    "django>=4.0",
    "python-dateutil",
]
requires-python = ">=3.9"

And there is a note in the project file itself about Python 3.14 support requiring updates for the module, with the corresponding classifier commented out and a to do marker. That is a small piece of housekeeping worth knowing about if you deploy on a very recent Python.

When to reach for it, and when to write the column yourself

It is worth being blunt about the case, because recurring dates are one of those problems that look like they need a library and often do not.

The case for using this package is that you have already decided to store a recurrence pattern, you want dateutil.rrule semantics, and you want a model field and an editor rather than a text column and a hand-rolled form. If all three of those are true, this package is a reasonable amount of code not to write.

The case against it is a model that stores an interval instead of a pattern. A course that meets every Tuesday for eight weeks, or a shift that repeats every four hours during a rota, has a simple numeric column. A recurrence pattern with exceptions, counts and interval combinations is a different problem, and that is the one this package addresses.

There is also a middle case that is easy to get wrong, and it is the one the documentation example walks through. The pattern and the time of day are separate facts. An event that starts at 2pm every Friday, and an event that starts at 6pm every other Friday, share a recurrence pattern and differ in the time. If your domain conflates them, you will end up storing a pattern that already contains the time, and then the field is the wrong shape for what you are modelling.

A related trap is time zones, and the documentation does not raise it in the visible part of the README. A weekly meeting at 9am is 9am in which zone, and what happens when the zone changes, is a modelling question that sits above this package. The field serialises a rule; it does not decide what the rule means for a given date.

The Jazzband context is the other thing that makes this an easy choice. These packages are held to shared guidelines for supported versions, testing, documentation and releases, which means you are not adopting one person's script. The trade is the dependency itself, which is small: Django and python-dateutil.

Maintenance, packaging and how the project is run

The repository is a well-maintained Jazzband package, and the details of how it is maintained tell you what to expect.

The tooling is current. The build backend is pdm-backend, with a pdm.lock file in the repository and a distribution flag set in the project configuration. The development dependencies are pinned exactly, including flake8, pytest-cov, pytest-django, pytest-sugar, pytest, sphinx and tox-pdm, which is the standard Jazzband arrangement for reproducible test runs.

The top level listing shows the standard shape: a recurrence/ package, a tests/ directory, a docs/ directory, a pyproject.toml, a pdm.lock, a tox.ini, a pytest.ini, a MANIFEST.in, a Makefile, a CHANGES.rst, a CONTRIBUTING.rst and a .pre-commit-config.yaml.

The Makefile is conventional and worth reading as a description of the workflow:

make
coverage:
	pytest

test:
	pytest

testall:
	tox

The build target runs the setup.py sdist and bdist_wheel, which is the older style of packaging, and the release target runs twine upload to PyPI.

The contribution guidance is specific and practical. It is best to separate proposed changes and pull requests into small, distinct patches by type so that they can be merged faster and released quicker, with the suggested split being bugfixes, new features, code and design improvements, documentation improvements, and tooling and CI improvements. Merging requires passing the checks configured with CI, which means running tests and linters successfully on the currently officially supported Python and Django versions.

The test automation runs on GitHub Actions, and you can run it locally with the tox command before pushing commits. Bug reports go to the GitHub issue tracker, and contributions are under the Jazzband Contributor Code of Conduct and guidelines, which is the part of the package that matters most for a library you are putting into a production application.

Editorial conclusion

Adopt django-recurrence if you are storing a recurring schedule in a Django model and want dateutil.rrule semantics rather than a homegrown interval column, since the field handles serialisation and the widget gives you an editor. Do not adopt it to store the time an event happens, because the field deliberately deals only with recurrences and you have to keep start and end times in your own fields. Before you add it, note that the package supports Django 4.2, 5.2 and 6.0 with Python 3.10 to 3.13, and that Python 3.14 support is still listed as a to do in the project metadata.

Frequently asked questions

What does django-recurrence do?

It provides Recurrence and Rule objects using a subset of rfc2445, wrapping dateutil.rrule, a RecurrenceField model field that serializes recurrence information for database storage, and a JavaScript widget.

Does the RecurrenceField store the time an event happens?

No. The field deals only with recurrences, not with specific time information, so you keep start and end times in your own fields. An event starting at 2pm with recurrences of every Friday stores the pattern in the field and the times separately.

Which Django and Python versions does django-recurrence support?

The project metadata lists Django 4.2, 5.2 and 6.0, with requires-python of 3.9 or later and classifiers for Python 3.10 to 3.13. Python 3.14 support is noted as requiring updates for the module and its classifier is commented out with a to do marker.

How do I install and test django-recurrence?

It is published on PyPI as django-recurrence. The repository uses pdm-backend with a pdm.lock, and the documented commands are pytest for a single run, tox for all supported versions via the testall target, and twine upload for the release target.

What does the django-recurrence project require from contributors?

Small, distinct patches separated by type, so bugfixes, new features, code improvements, documentation and tooling changes can be merged and released faster, and the CI checks must pass, meaning tests and linters run successfully on the currently supported Python and Django versions. The test suite runs on GitHub Actions and can be run locally with tox.

Official sources

  1. jazzband/django-recurrence on GitHub
  2. License: BSD-3-Clause
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/jazzband-django-recurrence.svg)](https://hysenlabs.com/projects/jazzband-django-recurrence)

Was this useful?