Factorizer/Cluster ERP WikiCluster ERP Türkçe
Home / Platform

Explanation

Working time

The time-zone-aware working-time engine in core/working_time.py with weekly shifts, date overrides, calendar exceptions, base-calendar inheritance, and DST handling.

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 Shifts map 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, and inherit controls, 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 localize docstring states that a plain combination with zoneinfo shifted 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 calendars reach calendars over the EventBus with GetCalendarWindows, GetEffectiveWorkingCalendar, GetEffectiveWorkingCalendars, and GetOwnerWorkingDays, and production listens to WorkingCalendarChanged (src/cluster_erp/core/contracts.py:1980-2037). See contracts reference.

Interaction with other parts

  • core.working_time is imported by 8 files in calendars, owned_organisations, and production (counted with grep -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_set treats 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

Verified against commit 92f8051 on 2026-11-13.

Verified against commit 92f8051 · 2026-11-13