Softanza
DémarrerEnglish
Softanza
Recherche · narration

Time Capacity Planning in Softanza: Mastering Constraints with stzCalendar

Softanza's stzCalendar rethinks this with a pragmatic intuition: time as a finite, constraint-driven resource, much like a computational budget. You declare rules—boundaries, working days, holidays, breaks—and the system computes realistic, usable capacity automatically. Events plug in via stzTimeLine, which handles timelines and spans, while stzCalendar acts as the constraint engine, ensuring events fit available slots or reveal impossibilities upfront.

par Mansour Ayouni · dans le dépôt depuis le 2025-10-12 · tutoriel · sans domaine de l'Atlas

Cet article est écrit en anglais, comme la bibliothèque.

Most scheduling tools treat time as a grid—a static calendar of slots you fill with events, often leading to manual guesswork and overlooked conflicts.

This declarative approach eliminates tedious calculations, fostering transparent, auditable planning. It elevates calendars from passive date trackers to active reasoning tools, aligning with Softanza's philosophy of intent-based, composable constructs that mirror natural thinking in code.


A Paradigm Shift: From Events to Constraint Composition

In stzCalendar, available time emerges layer by layer, like stacking filters in a query pipeline:

text

constraint input → filter/reduce → output → next layer input

Each layer refines capacity from the prior one—declarative, automatic, and traceable.

  1. Boundary – Set your calendar's scope to prevent leaks.
  2. Filter – Exclude non-working days like weekends and holidays.
  3. Quantify – Convert days to hours based on business schedules.
  4. Subtract – Deduct breaks, meetings, or maintenance windows.
  5. Query – Probe any layer to trace the logic.

This isn't abstract; it's a practical mental model for composable time reasoning, where constraints build meaning without complexity.

Here’s what it looks like in practice:

╭──────────────────────────────────────────────────────────────────────╮
│ CALENDAR BOUNDARY: [2024-10-01 to 2024-10-31] = 31 days                  
│                                                                           
│ ╭────────────────────────────────────────────────────────────────╮  
│ │ LAYER 2: WORKING DAYS FILTER (Mon-Fri)                                
│ │ Input: 31 days → Output: 23 days (weekends: 8 removed)               
│ │                                                                      
│ │ ╭─────────────────────────────────────────────────────────────╮ 
│ │ │ LAYER 3: HOLIDAYS FILTER (Oct 5 = Independence Day)                
│ │ │ Input: 23 days → Output: 23 days (Oct 5 is already weekend)      
│ │ │                                                             
│ │ │ ╭─────────────────────────────────────────────────────────╮
│ │ │ │ LAYER 4: BUSINESS HOURS (09:00-17:00 = 8h/day)          
│ │ │ │ Input: 23 days × 8h → Output: 184 hours                 
│ │ │ │                                                           
│ │ │ │ ╭────────────────────────────────────────────────────╮ 
│ │ │ │ │ LAYER 5: BREAKS (Lunch 12:00-13:00 = -1h/day)          
│ │ │ │ │ Input: 184 hours → Output: 161 hours                 
│ │ │ │ │ (23 days × 7 hours/day after lunch)                  
│ │ │ │ │                                                         
│ │ │ │ │ ╭────────────────────────────────────────────────╮ 
│ │ │ │ │ │ LAYER 6: CUSTOM CONSTRAINTS
│ │ │ │ │ │ (Maintenance: Wed 14:00-16:00 = -2h/day)
│ │ │ │ │ │ Wednesdays: 7h - 2h = 5h usable
│ │ │ │ │ │ Other days: 7h (unchanged)
│ │ │ │ │ │
│ │ │ │ │ │ ╭───────────────────────────────────────────╮
│ │ │ │ │ │ │ FINAL CAPACITY
│ │ │ │ │ │ │ Total: 161h (with maintenance accounted for)
│ │ │ │ │ │ │ Per day: 5-7h (depending on day of week)
│ │ │ │ │ │ │ Auditable: Every number traced to constraints
│ │ │ │ │ │ ╰───────────────────────────────────────────╯
│ │ │ │ │ ╰────────────────────────────────────────────────╯
│ │ │ │ ╰────────────────────────────────────────────────────╯
│ │ │ ╰────────────────────────────────────────────────────────╯
│ │ ╰────────────────────────────────────────────────────────────╯
│ ╰────────────────────────────────────────────────────────────────╯
│                                                                
╰─────────────────────────────────────────────────────────────────────╯

Key insight: Key insight: Alter any rule—a new holiday, adjusted break, or custom constraint—and capacity recalculates instantly. The model ensures full traceability, turning scheduling into a logical, feasibility-checked process rather than improvisation.

In the next section, we’ll build this same logic in code and watch how each layer interacts, starting with the simplest boundary definition.

The Layered Constraint Model

Time availability isn't binary—it's layered. Start with a month, peel away weekends, remove holidays, subtract breaks, then apply custom maintenance windows. Each layer reduces capacity; all layers compound into your final, auditable answer.

Layer 1: The Calendar Boundary

Haro
oCal = new stzCalendar([2024, 10])

oCal {
    ? Start()
    #--> 2024-10-01
    ? StartQ().DayName()
    #--> Tuesday

    ? End_()
    #--> 2024-10-31
    ? EndQ().DayName()
    #--> Thursday

    ? Year()
    #--> 2024
    
    ? MonthName()
    #--> October
}

illustration

October 2024 spans 31 days, Tuesday to Thursday. This fences your scope—you can't accidentally leak into September or November. All queries stay within this boundary.

Layer 2: Working Days

Haro
oCal.SetWorkingDays(["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"])

oCal {
    ? IsWorkingDay("2024-10-03")  #--> 1 (TRUE, Thursday)
    ? IsWorkingDay("2024-10-05")  #--> 0 (FALSE, Saturday)
    ? HowManWorkingDays()         #--> 23
}

illustration

October 2024 has 31 days, but only 23 are working days (Mon-Fri). Weekends (Oct 5-6, 12-13, 19-20, 26-27 = 8 days) are auto-filtered. All downstream calculations skip weekends automatically.

Layer 3: Holidays

Haro
oCal {
    AddHoliday("2024-10-05", "Independence Day")
    AddHoliday("2024-10-15", "National Day")
    
    ? IsHoliday("2024-10-05")     #--> 1 (TRUE)
    ? HolidayName("2024-10-05")   #--> Independence Day
    ? HowManyHolidays()           #--> 2
}

illustration

Oct 5 is a Saturday (already excluded as a weekend), so adding it as a holiday doesn't further reduce working days—it's logged for context. Oct 15 is a Tuesday, which would reduce working days to 22 if it fell mid-week. Holidays layer on top of weekends.

Layer 4: Business Hours and Breaks

Haro
oCal {
    SetBusinessHours("09:00:00", "17:00:00")
    AddBreak("12:00:00", "13:00:00", "Lunch")
    
    ? AvailableHoursN()  #--> 161
    ? AvailableDaysN()   #--> 23
}

illustration

With 8-hour days (9-17) minus 1-hour lunch breaks:

  • 23 working days × 7 hours/day = 161 total available hours

Each working day yields 7 usable hours. This compounds through layers: start with 31 calendar days → filter to 23 working → multiply by 7 hours per day = 161.

Layer 5: Custom Constraints

Haro
oCal {
    AddConstraint("MaintWindow", [:Every, :Wednesday, :From, "14:00", :To, "16:00"])
    
    ? ApplyConstraints("2024-10-09")  #--> 5 (not 7)
}

illustration

Oct 9 is a Wednesday. Normally 7 hours (9-17, minus lunch). But the maintenance window (2-4pm, 2 hours) further reduces it to 5 usable hours. Constraints layer recursively.

Synthesis: Query Usable Time Across Ranges

Haro
oCal {
    ? AvailableHoursOn("2024-10-10")              #--> 7 (Thursday, no constraints)
    ? AvailableHoursBetween("2024-10-01", "2024-10-15")  #--> 77
}

illustration

Oct 1-15 spans 15 calendar days, but only 11 are working days (excluding 4 weekends), yielding 11 × 7 = 77 available hours. Every number is justified by the constraint stack.


Task Fitting: Making Decisions

With capacity quantified, fit tasks objectively:

Haro
oCal {
    ? CanFit("2024-10-10", "5")   #--> TRUE (7 hours available)
    ? CanFit("2024-10-10", "10")  #--> FALSE (only 7 available)
    ? @@NL(FirstAvailableSlot("4"))
    #--> [ "2024-10-01 09:00:00", "2024-10-01 13:00:00" ]
}

illustration

A 5-hour task fits on Oct 10 (7 hours available). A 10-hour task doesn't. A 4-hour task starts Oct 1 at 9am, ends at 1pm (after lunch break). Decisions are transparent and auditable.

Visualization: Making Time Tangible

Three views help spot bottlenecks and communicate constraints to teams.

Grid View

Haro
oCal.Show()
#-->
                October 2024
╭─────────────────────────────────────────╮
│ Mon   Tue   Wed   Thu   Fri   Sat   Sun │
├─────────────────────────────────────────┤
│        1     2     3     4    [5]   ░░  │
│  7     8     9     10    11   ░░    ░░  │
│  14    15    16    17    18   ░░    ░░  │
│  21    22    23    24    25   ░░    ░░  │
│  28    29    30    31                   │
╰─────────────────────────────────────────╯

Legend:
  [D] = Holiday
  ░ = Weekend

illustration

The grid shows at a glance: weekends (â–') form a repeating pattern, Oct 5 is marked [D] (holiday on weekend). Working days are clean. Easy to spot the rhythm.

Heatmap: Weekly Capacity

Haro
October 2024 - Capacity Heat Map

Week 1:  ▓▓▓▓▓ (5/5 days available)
Week 2:  ▓▓▓▓▓ (5/5 days available)
Week 3:  ▓▓▓▓▓ (5/5 days available)
Week 4:  ▓▓▓▓▓ (5/5 days available)
Week 5:  ▓▓▓░░ (3/5 days available)

Legend:
  ▓ = Available working day
  ░ = Weekend or holiday

illustration

Week 5 dips to 3/5 days (Oct 28-31: Mon-Thu), ideal for light tasks or buffers.

Detailed Table: Day-by-Day

Haro
oCal.ShowTable()
#-->
October 2024 - Detailed View

╭────────────┬───────────┬───────────────────┬───────────────────┬───────────╮
│    Date    │    Day    │     Business      │      Breaks       │ Available │
├────────────┼───────────┼───────────────────┼───────────────────┼───────────┤
│ 2024-10-01 │ Tuesday   │ 09:00:00-17:00:00 │ 12:00:00-13:00:00 │ 7h        │
│ 2024-10-02 │ Wednesday │ 09:00:00-17:00:00 │ 12:00:00-13:00:00 │ 7h        │
│ 2024-10-03 │ Thursday  │ 09:00:00-17:00:00 │ 12:00:00-13:00:00 │ 7h        │
│ 2024-10-04 │ Friday    │ 09:00:00-17:00:00 │ 12:00:00-13:00:00 │ 7h        │
│ 2024-10-05 │ Saturday  │ HOLIDAY           │                   │ 0h        │
│ 2024-10-06 │ Sunday    │ WEEKEND           │                   │ 0h        │
     ...          ...            ...                  ...          ...
│ 2024-10-28 │ Monday    │ 09:00:00-17:00:00 │ 12:00:00-13:00:00 │ 7h        │
│ 2024-10-29 │ Tuesday   │ 09:00:00-17:00:00 │ 12:00:00-13:00:00 │ 7h        │
│ 2024-10-30 │ Wednesday │ 09:00:00-17:00:00 │ 12:00:00-13:00:00 │ 7h        │
│ 2024-10-31 │ Thursday  │ 09:00:00-17:00:00 │ 12:00:00-13:00:00 │ 7h        │
╰────────────┴───────────┴───────────────────┴───────────────────┴───────────╯

Summary:
  Total Days: 31
  Working Days: 23
  Available Hours: 161

illustration

Each row shows exactly why hours are available (or zero). HOLIDAY and WEEKEND labels make exclusions explicit.

Comparison and Navigation

What If: Compare Two Periods

Haro
oCal1 = new stzCalendar([2024, 10])
oCal1.SetWorkingDays(["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"])

oCal2 = oCal1.Copy()
oCal2.GotoNextMonth()

? oCal1.Current()  #--> October 2024
? oCal2.Current()  #--> November 2024

? @@NL(oCal1.CompareWith(oCal2))
#-->
# [
#     [ "Metric", "This Calendar", "Other Calendar", "Difference" ],
#     [ "Total Days", 31, 30, 1 ],
#     [ "Working Days", 23, 21, 2 ],
#     [ "Available Hours", 184, 168, 16 ],
#     [ "Holidays", 0, 0, 0 ],
#     [ "Total Weeks", 5, 5, 0 ]
# ]

illustration

October has 31 days (23 working), November has 30 (21 working). That's a 16-hour gap (2 fewer working days × 8 hours). Use this for sprint planning: tighter capacity in November means fewer stories.

Timeline Integration: Events Meet Constraints

Overlay events to detect conflicts against constraints:

Haro
oCal = new stzCalendar([2024, 10])
oCal {
    SetWorkingDays(["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"])
    AddHoliday("2024-10-05", "Independence Day")
}

oTimeline = new stzTimeLine("2024-10-01", "2024-10-31")
oTimeline.AddPoint("STANDUP", "2024-10-10 09:00:00")
oTimeline.AddSpan("PROJECT", "2024-10-15", "2024-10-20")

oCal.MarkTimeline(oTimeline)
oCal.Show()
#-->
'
                October 2024
╭─────────────────────────────────────────╮
│ Mon   Tue   Wed   Thu   Fri   Sat   Sun │
├─────────────────────────────────────────┤
│        1     2     3     4    ░░    ░░  │
│  7     8     9    ●10    11   ░░    ░░  │
│  14   ▬15   ▬16   ▬17   ▬18   ▬░░   ▬░░ │
│  21    22    23    24    25   ░░    ░░  │
│  28    29    30    31                   │
╰─────────────────────────────────────────╯

Legend:
  ░ = Weekend
  ● = Timeline-event
  ▬ = Timeline-span

╭───────────────────────┬─────────────────────╮
│        Metric         │        Value        │
├───────────────────────┼─────────────────────┤
│ Total Days            │                  31 │
│ Working Days          │                  23 │
│ Weekend Days          │                   8 │
│ Holidays              │                   0 │
│ Total Available Hours │                 184 │
│ Average Hours Per Day │                   8 │
│ First Working Day     │ 2024-10-01          │
│ Last Working Day      │ 2024-10-31          │
│ Business Hours        │ 09:00:00 - 17:00:00 │
╰───────────────────────┴─────────────────────╯

illustration

The STANDUP event (●) lands on Oct 10, a Thursday (working day). The PROJECT span (▬) runs Oct 15-20, crossing into weekend days (19-20, marked ▬░░). The visualization immediately shows the conflict—the project touches non-working days, requiring awareness.

Haro
? oCal.ConflictsWith(oTimeline)          #--> TRUE
? @@NL(oCal.TimelineEvents())
#-->
# [
#     [ "points", [ [ "STANDUP", "2024-10-10 09:00:00" ] ] ],
#     [ "spans", [ [ "PROJECT", "2024-10-15 00:00:00", "2024-10-20 00:00:00" ] ] ]
# ]

illustration

No silent failures—if a milestone touches a weekend or holiday, ConflictsWith() flags it explicitly.

Real-World Scenario: Sprint Feasibility

Combine layers to validate sprints:

Haro
oCal = new stzCalendar([2024, 10])
oCal {
    SetWorkingDays(["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"])
    SetBusinessHours("09:00:00", "17:00:00")
    AddBreak("12:00:00", "13:00:00", "Lunch")
    AddHoliday("2024-10-05", "Independence Day")
}

aTasks = [
    ["Design", 40],
    ["Development", 80],
    ["Testing", 32]
]

nRequired = 0
for aTask in aTasks
    nRequired += aTask[2]
next

nAvailable = oCal.AvailableHoursN()

if nRequired <= nAvailable
    ? "âœ" Sprint fits: " + nRequired + "h needed, " + nAvailable + "h available"
    #--> âœ" Sprint fits: 152h needed, 161h available
else
    ? "✗ Capacity exceeded by " + (nRequired - nAvailable) + " hours"
ok

illustration

152 hours required; 161 available (23 working days × 7 hours). Sprint fits with 9-hour buffer. This calculation is bulletproof: every hour is justified by the constraint stack.

Querying Details: Drilling Deeper

Single Date Info

Haro
oCal {
    ? @@NL(DateInfo("2024-10-03"))
    #-->
    # [
    #     [ "date", "2024-10-03" ],
    #     [ "isWorkingDay", 1 ],
    #     [ "isHoliday", 0 ],
    #     [ "availableHours", 7 ]
    # ]
}

illustration

Oct 3 is Thursday (working day, 7 hours). Use this for scheduling individual tasks.

Range Summary

Haro
? @@NL(RangeInfo("2024-10-01", "2024-10-15"))
#-->
# [
#     [ "startDate", "2024-10-01" ],
#     [ "endDate", "2024-10-15" ],
#     [ "totalDays", 15 ],
#     [ "workingDays", 11 ],
#     [ "weekendDays", 4 ],
#     [ "holidays", 0 ],
#     [ "availableHours", 77 ],
#     [ "overlappingEvents", [ ] ]
# ]

illustration

First half of October: 15 days span, but only 11 are working (4 weekend days excluded), yielding 77 hours. Events field shows conflicts if timeline is marked.

Data Export and Integration

Hash Export

Haro
? @@NL(oCal.ToHash())
#-->
# [
#     [ "startdate", "2024-10-01" ],
#     [ "enddate", "2024-10-31" ],
#     [ "year", 2024 ],
#     [ "month", 10 ],
#     [ "totaldays", 31 ],
#     [ "workingdays", 23 ],
#     [ "availablehours", 184 ],
#     [ "workingdayslist", [ 1, 2, 3, 4, 5 ] ],
#     [ "holidays", [ [ "2024-10-05", "Independence Day" ] ] ],
#     [ "breaks", [ [ "12:00:00", "13:00:00", "Lunch" ] ] ],
#     [ "businessstart", "09:00:00" ],
#     [ "businessend", "17:00:00" ]
# ]

illustration

Full config exported as a hash—load into reports, APIs, or databases.

Statistics

Haro
? @@NL(oCal.Stats())
#-->
# [
#     [ "metric", "value" ],
#     [ "Total Days", 31 ],
#     [ "Working Days", 23 ],
#     [ "Weekend Days", 7 ],
#     [ "Holidays", 1 ],
#     [ "Total Available Hours", 161 ],
#     [ "Average Hours Per Day", 7 ],
#     [ "First Working Day", "2024-10-01" ],
#     [ "Last Working Day", "2024-10-31" ]
# ]

illustration

One-line stats for dashboards or reports.

Natural Language Orientation: Intuitive API Design

Softanza prioritizes methods that read like English, with consistent naming conventions.

Pattern: Base, Count, Existence

For collections (days, holidays, breaks):

  • Base method (e.g., WorkingDays()) returns a list
  • Suffixed variant (e.g., WorkingDaysN()) returns the count
  • Prefixed variant (e.g., ContainsWorkingDays() or alias HasWorkingDays()) returns a boolean
Haro
oCal {
    aWorkingDays = WorkingDays()                  # List of dates
    nWorkingDays = WorkingDaysN()                 # Count: 23
    bHasWorking = ContainsWorkingDays()           # Boolean: 1 (TRUE)
}

illustration

This is consistent across the API:

Collection List Count Boolean
Working Days WorkingDays() WorkingDaysN() ContainsWorkingDays()
Holidays Holidays() HolidaysN() ContainsHolidays()
Breaks Breaks() BreaksN() ContainsBreaks()
Available Days AvailableDays() AvailableDaysN() ContainsAvailableDays()
Weekends Weekends() WeekendsN() ContainsWeekends() / HasWeekends()

Power of Consistency: Your code reads naturally. No guessing whether a method returns a number or a list. If you see the N suffix, you know it's a count. If you see Contains or Has, you know it's a boolean check.

Comparative Analysis: stzCalendar vs. Other Ecosystems

Feature Softanza (stzCalendar) Python (calendar + pandas) JavaScript (Luxon + FullCalendar) Java (Joda-Time) Ruby (ActiveSupport)
Calendar Boundaries ✅ Validated scopes ◯ Manual ranges ✅ Luxon intervals ✅ JodaTime intervals ✅ Date ranges
Working Days Config ✅ Declarative, recalculates auto ◯ Custom loops ✅ FullCalendar business hours ◯ Custom code ◯ Extensions
Holiday Management ✅ Add, query, name ◯ External libs (holidays) ◯ Event overlays ◯ Custom code ✅ holidays gem
Business Hours/Breaks ✅ Granular, auto-subtracted ◯ timedeltas (manual) ✅FullCalendar constraints ◯ Durations only ◯ Manual math
Capacity Calculations ✅ Hours/days/ranges, task fitting ◯ Custom scripts ◯ Event durations only ✅ Basic arithmetic ◯ Manual time math
Visualizations ✅ Grid + heatmap + table (ASCII) ◯ Matplotlib plots ✅FullCalendar UI ✅ Text dump only ✅ CLI text
Timeline Integration ✅ Mark events, detect conflicts ◯ Pandas timelines (post-hoc) ✅FullCalendar events ◯ Intervals (no conflicts) ◯ Ranges only
Multi-Calendar Compare ✅ Side-by-side metrics ◯ DataFrame diffs ◯ Not standard ◯ Not standard ◯ Not standard
Export Formats ✅ Hash/JSON/CSV ✅CSV/JSON ◯ JSON only ◯ String serialization ✅ JSON
Planning-First Design ✅ Built-in reasoning engine ◯ Requires assembly ◯ UI-centric, not code-centric ✅ Low-level primitives ◯ Extension-based

Key Differences:

  • Softanza: Constraints are first-class, baked into every query. Capacity is computed on demand, not manually. Visualizations are integrated. Timeline conflicts are detected automatically.
  • Python: Powerful data manipulation, but requires glue code to tie calendar, holidays, and business hours together. pandas excels at analysis but has no capacity-planning primitives.
  • JavaScript: FullCalendar dominates UI-based scheduling. Luxon handles date math well. But reasoning about constraints is delegated to application code; no built-in capacity engine.
  • Java/Joda-Time: Low-level, precise, but verbose. You build calendars from primitives—no opinions, no shortcuts.
  • Ruby: ActiveSupport provides helpers, but capacity planning requires external gems or custom code.

The Paradigm Gap:

Softanza's key differentiator isn't features—it's philosophy. Other tools treat constraints as data to manage; Softanza treats them as a composable reasoning engine. When you add a holiday or break, everything recalculates because the constraints are wired into the model, not bolted on. Python, JS, Java, Ruby all require you to build that wiring yourself. This makes Softanza ideal for time-driven applications where capacity must be continuously queryable and justified.

Conclusion

stzCalendar reimagines time as a concrete, queryable asset rather than an abstract liability, much like Softanza's intent-based paradigms in file handling or data crafting. Its layered constraints (boundaries → working days → holidays → business hours → breaks → custom rules) compound predictably, surfacing usable capacity while detecting hidden losses. Visualizations highlight patterns, comparisons uncover trade-offs, and exports enable seamless integration.

Citer

Mansour Ayouni. « Time Capacity Planning in Softanza: Mastering Constraints with stzCalendar ». Softanza narrations, 2025. https://mayouni.github.io/stzsite/fr/narrations/stzcalendar-time-capacity-planning-in-softanza.html

Non exécuté : son code touche aux fichiers, au réseau, à l'horloge ou au hasard. L'article est publié tel qu'il est écrit ; ses blocs sont des illustrations. Le code des articles est du code Haro. Là où un article a été écrit avant que la langue prenne son nom actuel, le nom a été changé et rien d'autre. Genre, domaine et série lus dans le nom du fichier, année tirée du premier commit : dérivés, jusqu'à ce que l'article porte son propre en-tête. Le fichier sur GitHub(s'ouvre dans un nouvel onglet)