PhoenixLiveCalendar.Utils.Constraints (PhoenixLiveCalendar v0.1.0)

Copy Markdown View Source

Booking constraint validation logic.

Pure Elixir functions for validating whether a proposed booking is allowed given a set of constraints, existing events, and availability windows. No database required — works with in-memory data.

Summary

Functions

Returns all events that conflict with a proposed time range.

Checks if a proposed time range overlaps with any existing events.

Counts how many bookings overlap with a given time range.

Snaps a DateTime to the nearest slot boundary.

Snaps a time to the nearest slot boundary based on the slot interval.

Validates a proposed booking against all constraints.

Types

validation_result()

@type validation_result() :: :ok | {:error, reason :: atom(), message :: String.t()}

Functions

conflicting_events(start_dt, end_dt, events)

@spec conflicting_events(DateTime.t(), DateTime.t(), [PhoenixLiveCalendar.Event.t()]) ::
  [
    PhoenixLiveCalendar.Event.t()
  ]

Returns all events that conflict with a proposed time range.

has_overlap?(start_dt, end_dt, config, events)

Checks if a proposed time range overlaps with any existing events.

Accounts for buffer times defined in the config.

overlap_count(start_dt, end_dt, events)

Counts how many bookings overlap with a given time range.

Used for capacity/seat checking.

snap_datetime_to_slot(dt, slot_minutes)

@spec snap_datetime_to_slot(DateTime.t(), pos_integer()) :: DateTime.t()

Snaps a DateTime to the nearest slot boundary.

snap_to_slot(time, slot_minutes)

@spec snap_to_slot(Time.t(), pos_integer()) :: Time.t()

Snaps a time to the nearest slot boundary based on the slot interval.

Examples

iex> Constraints.snap_to_slot(~T[10:17:00], 15)
~T[10:15:00]

iex> Constraints.snap_to_slot(~T[10:23:00], 15)
~T[10:15:00]

iex> Constraints.snap_to_slot(~T[10:38:00], 15)
~T[10:30:00]

validate_booking(start_dt, end_dt, config, existing_events, opts \\ [])

Validates a proposed booking against all constraints.

Returns :ok if the booking is valid, or {:error, reason, message} if not.

Parameters

  • start_dt — Proposed start DateTime
  • end_dt — Proposed end DateTime
  • config — BookingConfig with constraints
  • existing_events — List of existing events to check for overlaps
  • opts — Additional options:
    • now — Current DateTime (default: DateTime.utc_now())
    • resource_id — Resource to check availability against
    • availabilities — List of Availability windows

Examples

iex> Constraints.validate_booking(
...>   ~U[2026-04-01 10:00:00Z],
...>   ~U[2026-04-01 10:30:00Z],
...>   %BookingConfig{duration: 30, min_notice: 60},
...>   existing_events
...> )
:ok

iex> Constraints.validate_booking(
...>   ~U[2026-04-01 10:00:00Z],
...>   ~U[2026-04-01 10:15:00Z],
...>   %BookingConfig{duration: 30, min_duration: 30},
...>   []
...> )
{:error, :too_short, "Booking must be at least 30 minutes"}