src/cluster_erp/core/working_time.py computes working windows, working minutes, and working days for scheduling and planning. It is pure Python with no database access; callers load calendar rows and pass plain structures in. This page explains its two layers. The calendar records that feed it belong to the calendars module; see calendars and scheduling.
In short¶
- The low-level layer works on a
Shiftsmap from weekday (0 is Monday) to wall-clock spans (src/cluster_erp/core/working_time.py:32-34). - Wall-clock times are converted to UTC per local day, with an explicit rule for daylight-saving transitions (
src/cluster_erp/core/working_time.py:94-112). - The calendar layer adds per-date exceptions,
open,closed, andinheritcontrols, and inheritance from a base calendar (src/cluster_erp/core/working_time.py:340-380). - An unknown or empty time zone falls back to UTC (
src/cluster_erp/core/working_time.py:115-122). - The defaults are Monday to Friday, 08:00 to 17:00, in UTC (
src/cluster_erp/core/working_time.py:66-69).
How it works¶
Low-level primitives¶
| Function | Behaviour | Source |
|---|---|---|
localize(day, wall, tz) |
Wall-clock time to an aware datetime; an ambiguous time takes the first occurrence, a skipped time moves forward by the gap | src/cluster_erp/core/working_time.py:94-112 |
resolve_timezone(name) and is_valid_timezone(name) |
A ZoneInfo, falling back to UTC; a validity check |
src/cluster_erp/core/working_time.py:115-132 |
shifts_from_rows(rows) |
Builds Shifts from rows with day_of_week, start_time, end_time |
src/cluster_erp/core/working_time.py:135-155 |
with_overrides(shifts, overrides) |
Attaches per-date UTC windows to a weekly pattern as DayWindowShifts |
src/cluster_erp/core/working_time.py:37-63 |
working_windows(shifts, tz_name, day) |
UTC windows of one local day; a date override wins over the weekly pattern | src/cluster_erp/core/working_time.py:158-191 |
next_working_instant(...) |
The first working instant at or after a moment, within the search horizon | src/cluster_erp/core/working_time.py:194-210 |
add_working_minutes(...) |
Consumes working minutes from a start, skipping gaps; a non-positive duration becomes MIN_BAR_MINUTES |
src/cluster_erp/core/working_time.py:213-256 |
working_minutes_between(...) |
Working minutes in a UTC interval | src/cluster_erp/core/working_time.py:259-283 |
day_capacity_minutes(...) and effective_day_capacity_minutes(...) |
Available minutes of a local day | src/cluster_erp/core/working_time.py:286-329 |
SEARCH_HORIZON_DAYS is 90 and bounds the slot search; an internal iteration guard stops a pathological shift map (src/cluster_erp/core/working_time.py:76-81).
Calendar layer¶
A CalendarLayer holds a weekly template, a time zone, a list of CalendarException objects, and an optional base layer (src/cluster_erp/core/working_time.py:370-379). A CalendarException has a recurrence (none, annual, or weekly) and a control (open, closed, or inherit) (src/cluster_erp/core/working_time.py:344-367).
windows_for_day(layer, day) resolves one day in this order (src/cluster_erp/core/working_time.py:458-490):
flowchart TD
day["Local day"] --> exc{"Own exception matches?"}
exc -->|closed| none["No windows"]
exc -->|open with intervals| own["Exception intervals"]
exc -->|inherit| base["Resolve on base calendar"]
exc -->|no match| hol{"Base ancestor closes the day?"}
hol -->|yes| none
hol -->|no| tmpl["Own weekly template"]In prose: the layer's own most specific exception decides first; a one-off date beats an annual date, which beats a weekday rule (src/cluster_erp/core/working_time.py:410-433). Without an own exception, an explicit closed exception on a base ancestor closes the day; the base's weekly gaps do not propagate (src/cluster_erp/core/working_time.py:436-455). Otherwise the layer's weekly template applies. An open exception without intervals and an inherit exception without a base also fall through to the template (src/cluster_erp/core/working_time.py:478-484).
A shift whose end is not after its start ends on the next local day (src/cluster_erp/core/working_time.py:396-407). Inheritance depth is limited to 20 levels, which also guards against cycles (src/cluster_erp/core/working_time.py:83-85).
Further helpers: is_working_day, add_working_days (bounded loop), add_working_days_set (offsets dates against a pre-resolved set of open days), and preview_days (per-day windows and minutes for a date range) (src/cluster_erp/core/working_time.py:493-577).
Why it is built this way¶
- The module docstring states that the engine stays pure and unit-testable, with callers loading ORM rows, and that converting per calendar day makes DST correct by construction (
src/cluster_erp/core/working_time.py:1-21). - The
localizedocstring states that a plain combination withzoneinfoshifted whole shifts by an hour once a year (src/cluster_erp/core/working_time.py:95-104). - The docstring names the model it follows: a reusable weekly template, per-date exceptions, and base-calendar inheritance in the style of Microsoft Dynamics (
src/cluster_erp/core/working_time.py:12-17).
Rules and boundaries¶
- The module does not import any module or ORM model (
src/cluster_erp/core/working_time.py:23-30). - Callers outside
calendarsreach calendars over the EventBus withGetCalendarWindows,GetEffectiveWorkingCalendar,GetEffectiveWorkingCalendars, andGetOwnerWorkingDays, andproductionlistens toWorkingCalendarChanged(src/cluster_erp/core/contracts.py:1980-2037). See contracts reference.
Interaction with other parts¶
core.working_timeis imported by 8 files incalendars,owned_organisations, andproduction(counted withgrep -rln core.working_time src/cluster_erp/modules).- The production scheduling board re-exports the primitives (
src/cluster_erp/core/working_time.py:9-10).
Limits and open points¶
add_working_days_settreats a day outside the resolved range as a working day (src/cluster_erp/core/working_time.py:530-531).- Holiday data is calendar content. Which holidays a deployment loads is not determined from code.
Terms introduced¶
| Term | Code identifier | Definition | Turkish term | Source |
|---|---|---|---|---|
| shifts | Shifts |
A map from weekday to wall-clock working spans. | vardiyalar | src/cluster_erp/core/working_time.py:32-34 |
| calendar layer | CalendarLayer |
A resolved calendar: template, exceptions, time zone, and optional base. | takvim katmanı | src/cluster_erp/core/working_time.py:370 |
| calendar exception | CalendarException |
A date rule that overrides the weekly working pattern of a calendar, opening or closing the days it covers. | takvim istisnası | src/cluster_erp/core/working_time.py:349 |
Related pages¶
Verified against commit 92f8051 on 2026-11-13.