Hello,
this one is urgent for us: we are about to ship the first release of a scheduling module built on TTMSFNCGanttChart (1.3.3.10, Delphi 13.1, VCL), and it produces start dates that are too early in a very common situation.
A finish-to-start link with a positive lag in work days gives a successor start that is one or more days too early when the predecessor ends right before a holiday. The holiday is counted as part of the lag.
Demo (attached, LagAfterNonWorkingTime.zip, VCL, Win32, no design-time components): the program runs by itself and logs, for three calendars and lags of 0 to 3 work days, the successor start, the expected start and the direct result of GetActualEndDateTime. In all scenarios WorkingHours are empty (day-based scheduling) and both tasks use whWorkDaysOnly. Result on our machine (also in the zip):
| Scenario | lag 1 | lag 2 | lag 3 |
|---|---|---|---|
| 1. Mon–Fri, holiday on Thu 2026-11-05, A ends Wed | B starts Fri (expected Mon) | ok | ok |
| 2. Mon–Fri, no holiday, A ends Fri | ok | ok | ok |
3. All seven days in WorkingDays, weekends as holidays, A ends Fri |
Mon (expected Tue) | Mon (expected Wed) | Tue (expected Thu) |
Lag 0 is correct everywhere. Scenario 3 is how our application works (we need single working Saturdays, so weekends come as holidays). Scenario 1 shows that a plain Mon–Fri calendar is affected as well, as soon as a holiday follows the predecessor.
Cause (VCL.TMSFNCGanttClasses.pas): CalculateTaskTimes → GetDelayedTime(t.ScheduledEnd, ATaskDependency.Delay) (line 3058) → TTMSFNCGanttWorkTime.GetActualEndDateTime (line 6876), called with A.ScheduledEnd – the first moment after A, which here is the start of the holiday.
- Without
WorkingHours,IsInWorkingHoursis True for any moment (line 6523). So the first window is[tempD, GetNextEnd(tempD)](line 6894), althoughtempDlies in a holiday. - If that window already contains enough work time for the lag, the loop ends at once and the result is
tempD + tl(line 6919) – counted from the start of the holiday, so the holiday days count as lag. Scenario 1, lag 1: Thursday 00:00 + 1 day = Friday. Scenario 3, lag 1: Saturday 00:00 + 1 day = Sunday, moved to Monday. - Only when the window is too short is
tempDmoved to a working start viaGetActualStartDateTime. That is why lags 2 and 3 are right in scenario 1, and why the normal weekend in scenario 2 is right: thereGetNextEndreturns Friday 23:59:59, which is beforetempD.
Suggested fix: move the start to the next working time before the loop, for example at the beginning of the if (ADateTime > 0) and (AWorkTimePolicy <> whAllTime) branch:
tempD := GetActualStartDateTime(ADateTime, AWorkTimePolicy);
The demo checks this with public methods only (GetActualStartDateTime before GetActualEndDateTime, column "suggested fix"): all 12 results are then correct. A start that already is a working moment is not changed, so the end calculation of a task itself stays as it is.
We cannot work around it from the application: the lag is evaluated inside CalculateTaskTimes / GetPlannedStartForDependency, which are not virtual. A fix in the next update would help us a lot.
Thanks!
LagAfterNonWorkingTime.zip (7.4 KB)