Skip to main content

Reusable Django time block & recurrence engine.

Project description

PyPI Stability Concurrency License

Timeblocks

A reusable Django library for creating and managing time blocks using safe, deterministic recurrence rules.

Designed for scheduling systems where correctness, idempotency, and data safety matter.



Stability & API Guarantees (v1.0+)

Starting from version 1.0.0, timeblocks provides the following guarantees:

Stable Public API

The following interfaces are considered stable and will not change in backward-incompatible ways without a MAJOR version bump:

  • Slot and SlotSeries models
  • RecurrenceType and EndType enums
  • SeriesService public methods

Internal APIs

Internal helpers, generators, and validation utilities are not part of the public API and may change between minor releases.

Versioning

timeblocks follows semantic versioning:

  • MAJOR — breaking changes
  • MINOR — new features or recurrence types
  • PATCH — bug fixes and correctness improvements

Why timeblocks?

Most scheduling implementations break when:

  • recurrence rules change
  • slots are regenerated
  • bookings must be preserved
  • timezones drift
  • duplicate slots appear
  • concurrent actions occur (booking vs updates)

timeblocks solves these problems by enforcing strict invariants:

  • slots are generated from immutable templates
  • destructive operations are explicit and scoped
  • locked (booked) slots are never modified
  • regeneration is safe and idempotent
  • all datetime values are normalized to UTC
  • concurrency is handled explicitly, not implicitly

Mental Model (Read This First)

timeblocks separates intent from reality.

  • SlotSeries represents intent

    “This resource should be available every Mon/Wed/Fri from 10–11”

  • Slot represents reality

    A concrete time interval that exists, may be booked, or may be cancelled

This separation is intentional and fundamental.

SlotSeries is the source of truth.
Slots are generated artifacts.

Slots must never be treated as authoritative configuration.


Core Concepts

SlotSeries (template)

A SlotSeries defines how slots should exist:

  • start date
  • time window
  • recurrence rule
  • termination condition

It does not represent bookings or history.

Slot (instance)

A Slot is a concrete time interval generated from a series.

Slots may be:

  • open
  • locked (e.g. booked)
  • soft-deleted (historical)

Once a slot is locked, it becomes immutable.


Lifecycle Semantics

A typical lifecycle looks like this:

  1. A SlotSeries is created
  2. Concrete Slot rows are generated
  3. Some slots become locked (e.g. booked)
  4. The series may be regenerated or cancelled
  5. Historical slots are preserved

Important rules:

  • Regeneration never modifies locked slots
  • Cancellation never deletes historical data
  • Slots are soft-deleted, never hard-deleted
  • Operations are safe to retry (idempotent)

Supported Recurrence Types

  • NONE — single occurrence
  • DAILY — every N days
  • WEEKLY — specific weekdays (e.g. Mon/Wed/Fri)
  • WEEKDAY_MON_FRI — Every weekday (Monday–Friday, preset)
  • WEEKDAYS — Custom weekdays (e.g. Mon/Wed/Fri)
  • MONTH_NTH — Nth weekday of the month (e.g. first Monday, third Friday)
  • MONTH_LAST — Last weekday of the month (e.g. last Friday)
  • YEARLY — Nth weekday of a specific month (e.g. first Monday of January)

Additional recurrence types can be added safely without breaking existing data.


Installation

pip install timeblocks

Add to Django settings:

INSTALLED_APPS = [
    ...
    "django.contrib.contenttypes",
    "timeblocks",
]

Run migrations:

python manage.py migrate

Basic Usage

from datetime import date, time
from timeblocks.services.series_service import SeriesService

series = SeriesService.create_series(
    owner=user,
    data={
        "start_date": date(2025, 1, 1),
        "start_time": time(9, 0),
        "end_time": time(10, 0),
        "timezone": "UTC",
        "recurrence_type": "DAILY",
        "interval": 1,
        "end_type": "AFTER_OCCURRENCES",
        "occurrence_count": 5,
    },
)

This will create:

  • one SlotSeries
  • five Slot rows
  • all timestamps normalized to UTC

Regenerating Slots

When a recurrence rule changes, regenerate safely:

from timeblocks.services.series_service import SeriesService

SeriesService.regenerate_series(
    series=series,
    scope="future",  # or "all"
)

Regeneration Rules

  • locked slots are never touched
  • soft-deleted slots are preserved
  • scope controls blast radius
  • operation is atomic and idempotent

Cancelling a Series

from timeblocks.services.series_service import SeriesService

SeriesService.cancel_series(series=series)

Effects:

  • series is deactivated
  • future unlocked slots are soft-deleted
  • past and locked slots remain intact

Invariants & Guarantees

timeblocks enforces the following invariants at all times:

  • a slot can never be booked twice
  • locked slots are immutable
  • regeneration is scoped and deterministic
  • cancellation preserves historical data
  • destructive operations are explicit
  • all writes are transactional
  • all datetime values are normalized to UTC

Violation of these invariants is considered a bug.


Concurrency & Safety

timeblocks is designed to be safe under concurrent access.

Key principles:

  • booking must use row-level locking (select_for_update)
  • regeneration and cancellation lock affected rows before mutation
  • destructive operations never race with bookings

Do not implement booking or regeneration logic outside the provided services unless you fully understand the concurrency implications.


Common Gotchas & Best Practices

❗ Django Context Required

timeblocks is a Django app. Models and services must be used inside a configured Django environment (e.g. manage.py shell).

❗ Soft Deletes

Slots are soft-deleted. Always query active availability with:

Slot.objects.filter(is_deleted=False)

❗ Do Not Edit Slots Directly

Slots are generated artifacts. Always mutate schedules via SlotSeries and service methods.

Use WEEKDAY_MON_FRI for a fixed Monday–Friday schedule. Use WEEKDAYS when you need custom weekday selection. MONTH_NTH supports only week_of_month values 1–4.

Rules like "5th Monday" are intentionally unsupported.

MONTH_LAST always refers to the final occurrence of a weekday in a month, not a fixed week number.

YEARLY recurrence does not support fixed calendar dates (e.g. Jan 15). Use MONTH_NTH with interval=12 if exact dates are required.


What timeblocks Does NOT Do

  • booking logic
  • payments
  • permissions
  • notifications
  • UI or API views

These belong in your application layer.


Public API Stability

The following interfaces are considered stable starting from v1.0:

  • Slot, SlotSeries models
  • SeriesService public methods
  • Published enums and query helpers

Internal modules and helpers are not part of the public API and may change without notice.


Compatibility

  • Django >= 3.2
  • Python >= 3.8
  • Database-agnostic (PostgreSQL, MySQL, SQLite)

Versioning & Upgrades

timeblocks follows semantic versioning.

  • PATCH releases fix bugs without changing behavior
  • MINOR releases add new recurrence types or capabilities
  • MAJOR releases may change behavior or contracts

Breaking changes are always documented in the changelog.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

timeblocks-1.0.0.tar.gz (17.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

timeblocks-1.0.0-py3-none-any.whl (17.8 kB view details)

Uploaded Python 3

File details

Details for the file timeblocks-1.0.0.tar.gz.

File metadata

  • Download URL: timeblocks-1.0.0.tar.gz
  • Upload date:
  • Size: 17.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.7

File hashes

Hashes for timeblocks-1.0.0.tar.gz
Algorithm Hash digest
SHA256 d0c64c9f131f840e9cb6d04fb85f11c62fe7958bf26e5659c253f84eeae516c3
MD5 c1ae3c1349c3dc7e22e6984f3560301f
BLAKE2b-256 617e386c4b693f360eebb697c1886cef7bcd47a5697f9b8a6708d67a0a7b7cef

See more details on using hashes here.

File details

Details for the file timeblocks-1.0.0-py3-none-any.whl.

File metadata

  • Download URL: timeblocks-1.0.0-py3-none-any.whl
  • Upload date:
  • Size: 17.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.7

File hashes

Hashes for timeblocks-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 67f78c894b03ee68f9b5c1e76d143eb3fc9b61e1da8c2f424c4f77fe395964d8
MD5 f857b1bfd7ad09432bd57821ffd71a64
BLAKE2b-256 a0fac68e8b1fd07deb2c23139a78d5242b05d945f45f4a31b3f2a843c4eb0cdb

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page