Pricing Rules by Date & Time
Specific Date & Time-Based Rule
Restaurants adjust package prices according to specific date/time windows.
| Aspect | Behavior |
|---|---|
| Approval | Always auto-approve |
| Condition | Uses the rule’s own Date Type/Period Type configuration |
| Action | By Percentage (%) or By Fixed Amount |
Date Types
Implementation: DateTimeCondition#date_type (pricing-engine/app/models/date_time_condition.rb:66).
| Date Type | Description | Required fields | Uses slots? |
|---|---|---|---|
everyday | Applies every day within the rule period | None (auto) | No |
specific_date | Specific calendar dates | ConditionItem.start_date, end_date | Yes |
specific_time | Specific time-of-day (uses rule’s start_date as the single day) | ConditionItem.start_time, end_time | Yes |
specific_date_time | Specific dates AND times | Date + time fields | Yes |
day_of_week | Specific weekdays recurring | DateTimeCondition.days_of_week[] | No |
day_time_of_week | Specific weekdays at specific times | days_of_week[] + ConditionItem times | Yes |
Day-of-week encoding
days_of_week is stored as a string array of integers (0=Sunday … 6=Saturday). The frontend can send mon, tue, … or numeric strings — both normalize to the integer form via DateTimeCondition#normalize_day_to_number (date_time_condition.rb:432).
Period Modes
PricingRule#period_mode controls whether a rule is a single occurrence or a recurring range.
| Mode | Behavior |
|---|---|
single | One occurrence only. end_date must equal start_date; is_no_end_date/is_no_end_time must be false. |
range | Recurring window. Supports open-ended (is_no_end_date = true) and explicit date ranges. |
When the parent rule is single, all conditions inherit single automatically (except recurring day_of_week / day_time_of_week, which keep their own period_mode since for them “single” means a single time-point, not a single date).
Condition Item (Slot)
Implementation: ConditionItem (pricing-engine/app/models/condition_item.rb).
A ConditionItem is one time-window where the rule’s adjustment applies. Multiple items = multiple windows in the same rule.
| Field | Type | Notes |
|---|---|---|
start_date / end_date | date | For slot types that use dates |
start_time / end_time | time | For slot types that use times |
is_no_end_date | boolean | Open-ended date range (covers all future dates) |
is_no_end_time | boolean | Open-ended time window (until 23:59) |
is_anytime | boolean | Time matches regardless of time-of-day |
Validation rules
start_date≤end_datestart_time≤end_time(same day only; multi-day ranges skip time order check)- Cannot create an item that ends in the past (
condition_item.rb:599) - Item range must fall within the parent rule’s range (
condition_item.rb:615) - Two items in the same rule cannot overlap (
condition_item.rb:530) - An ongoing item (
is_no_end_date) must be the only item in the rule singleperiod mode forbidsis_no_end_date,is_no_end_time, and multi-day items
Cross-condition overlap
DateTimeCondition#no_cross_condition_overlaps (date_time_condition.rb:557) catches overlaps across different DateTimeCondition records within the same PricingRule.
Evaluation Logic
Pricing::ConditionEvaluator (pricing-engine/app/services/pricing/condition_evaluator.rb) checks whether a target datetime matches a condition.
result = Pricing::ConditionEvaluator.call(
date_time_condition: condition,
target_datetime: Time.current,
target_date: Date.current
)
result.success? # true if any condition_item matches
result.data # { matches:, matched_items: [ids], condition_id: }
Evaluation order:
- Day-of-week filter — if
days_of_weekis set, the targetwdaymust be in the allowed list - Item scan — iterate
condition_items; return the IDs of items where:item.active_on_date?(target_date)(start/end date range, or rule’s dates as fallback)item.active_at_time?(target_datetime)(start/end time range, withis_anytimeshort-circuit)item.applies_to_day?(target_date)(delegates to condition’s day-of-week check)
Period Type Combinations (UI Matrix)
The frontend uses a date × period matrix to drive which fields are shown. Here is the complete mapping:
| Case | Date Type | Period Type | Child Input Fields | UI Constraint |
|---|---|---|---|---|
| 1 | everyday | (N/A) | None | Auto-calculated; runs every day between parent Start and End |
| 2 | specific_date | Single Date | Date picker | Disables dates outside parent boundary |
| 3 | specific_date | Range Date | Start Date, End Date, No End Date | Both pickers disable out-of-bounds dates; End Date also disabled before Start Date |
| 4 | specific_time | Single Time | Time (Start/End) | No pickers; applies to every valid day within parent boundary |
| 5 | specific_time | Range Time | Start Time, End Time, No End Time | No pickers; No End Time = active until 23:59 of that day |
| 6 | specific_date_time | Single Date | Start Date, Start Time, End Time, No End Time | Start Date picker disables out-of-bounds; Time fields set hours on that date |
| 7 | specific_date_time | Range Date | Start Date, End Date, Start/End Time, No End Date/Time | Both date pickers disable out-of-bounds; End Date disables pre-Start |
| 8 | day_of_week | (N/A) | Mon–Sun checkboxes | Auto-calculated; runs only on checked days within parent timeline |
| 9 | day_time_of_week | Single Time | Mon–Sun checkboxes + Time (Start/End) | Auto-calculated; selected hours on checked days within parent timeline |
| 10 | day_time_of_week | Range Time | Mon–Sun checkboxes + Start/End Time + No End Time | Same as Case 9 |
Parent-Child Date/Time Limitation
When a Child condition’s date range falls inside a Parent rule’s General Setting period, these 3 global rules apply:
Rule 1 — Disabled Dates (Poka-yoke)
Child calendar pickers grey out and disable any date before Parent’s Apply From or after Parent’s Valid Until.
Rule 2 — Soft Cap (No End Date)
Child No End Date checkbox is always clickable, even if Parent has a fixed end date.
If checked:
- UI shows helper text: “Caps at {Parent End Date} based on General Setting.”
- Backend automatically stops the rule at 23:59 on Parent’s end date
Rule 3 — Post-Edit Conflict (Retroactive Change)
User shrinks Parent Date Range after creating a valid Child condition:
- Do NOT delete the child rule
- Outline Child Rule block in Red
- Disable Save button
- Show error: “This date is outside the main rule period. Please adjust.”
Backend Execution Date Logic
| Parent (Valid Until) | Child (End Date) | Child UI | Final Execution End |
|---|---|---|---|
| 31 Dec 2025 | 30 Nov 2025 | Normal | 30 Nov 2025 |
| 31 Dec 2025 | Ongoing (checked) | Checkbox + “Caps at 31 Dec 2025…” | 31 Dec 2025 |
| Ongoing | 30 Nov 2025 | Normal | 30 Nov 2025 |
| Ongoing | Ongoing (checked) | Normal | Infinite |
Examples
Example 1 — Weekend Premium
- Date type:
day_of_week days_of_week:["fri", "sat"]- Action: Increase by 15%
Applies every Friday and Saturday within the rule’s date range.
Example 2 — Holiday Date Range
- Date type:
specific_date - Period: Range
start_date: 2026-12-20,end_date: 2026-12-31,is_no_end_date: false- Action: Increase by 20%
Example 3 — Happy Hour
- Date type:
day_time_of_week days_of_week:["mon", "tue", "wed", "thu", "fri"]- Time slot: 15:00–18:00
- Action: Decrease by 10%
Example 4 — New Year’s Eve
- Date type:
specific_date_time - Period: Single Date
- Date: 2026-12-31
- Time: 20:00–02:00 (next day) — note: for cross-midnight, use Range Time with separate time fields, or split into two specific_date_time conditions
Related
- Customer-facing flow (HH Pegasus): Dynamic Pricings 2.0 — Customer Booking Flow
- HH Server integration: Dynamic Pricings 2.0 — HH Server Integration
- pricing-engine customer API: Customer Booking API
- Engineering spec:
PricingEngine/006-DATE-TIME-CONDITIONS.md - Source models:
pricing-engine/app/models/date_time_condition.rb,pricing-engine/app/models/condition_item.rb - Evaluator service:
pricing-engine/app/services/pricing/condition_evaluator.rb - Sibling PRDs: Capacity & Demand, AI (Rapidcanvas)