Skip to content

ComputeFireTimesBetween modifies trigger's StartTimeUtc/EndTimeUtc unexpectedly #2722

Description

@nomba

Hello Quartz community! 👋

I've recently started introducing Quartz in our product and encountered with some issues. Here is one of this.
We allow user to specify some schedule for product tasks. The schedule converts to Quartz-triggers (one or a few). The schedule can be complex and to improve user experience, we provide an estimation feature that shows a preview of fire times for a given interval (from and to date times).
Initially, I relied on TriggerUtils.ComputeFireTimesBetween, but encountered unexpected behavior. The method modified StartTimeUtc and EndTimeUtc of trigger and I got "wierd" set of fire times. Here’s a test that reproduces the issue:

using Quartz;
using Quartz.Spi;
using Xunit.Abstractions;

namespace QuartzPitfalls;

public class TriggerUtilsTests
{
    private readonly ITestOutputHelper _testOutputHelper;

    public TriggerUtilsTests(ITestOutputHelper testOutputHelper)
    {
        _testOutputHelper = testOutputHelper;
    }
    
    [Theory]
    [InlineData("2026-01-01 08:00:00Z","2026-01-07 08:00:01Z")] // Estimation interval matches trigger start and end
    [InlineData("2026-01-01 07:50:00Z","2026-01-07 08:00:01Z")] // Estimation interval starts 10 minutes earlier than trigger
    public void Trigger_should_run_only_first_week_of_january_2026(string fromUtc, string toUtc)
    {
        // Arrange: Start in the first week of January 2026 for one week
        
        var startAt = DateTimeOffset.Parse("2026-01-01 08:00:00Z"); // Start at 8.00 exactly 
        var endAt = DateTimeOffset.Parse("2026-01-07 08:00:01Z");   // End after a week
        
        var trigger = TriggerBuilder.Create()
            .StartAt(startAt)
            .EndAt(endAt)
            .WithSimpleSchedule(x => x.WithIntervalInHours(24).RepeatForever()) // Each day
            .Build(); 
        
        ICalendar? calendar = null;

        // Act

        var from = DateTimeOffset.Parse(fromUtc);
        var to = DateTimeOffset.Parse(toUtc);
        var actualFireTimes = TriggerUtils.ComputeFireTimesBetween((IOperableTrigger) trigger, calendar, from, to);

        // Assert: Run each day at 8.00 exactly for one week 

        var expectedFireTimes = new[]
        {
            "2026-01-01 08:00:00Z",
            "2026-01-02 08:00:00Z",
            "2026-01-03 08:00:00Z",
            "2026-01-04 08:00:00Z",
            "2026-01-05 08:00:00Z",
            "2026-01-06 08:00:00Z",
            "2026-01-07 08:00:00Z",
        };

        foreach (var actualFireTime in actualFireTimes)
            _testOutputHelper.WriteLine($"{actualFireTime.UtcDateTime}");
        
        Assert.Equal(expectedFireTimes.Select(DateTimeOffset.Parse), actualFireTimes);
    }
}

If I shift start estimation interval then the schedule breaks:

Image

The trigger is supposed to run at 8:00 but it shifts start at to 07.50:

Image

I found the issue kinda related to my problem: #148
As far as I understand, this behavior is intentional and likely won’t change. But I’d like to propose:

  • Add extra method that do not modified existing StartTimeUtc and EndTimeUtc if they already set
  • May from and to should be renamed to start or end just to emphasize that the arguments of TriggerUtils.ComputeFireTimesBetween related to trigger properties

To work around the issue, I ended up writing my own version that builds on top of ComputeFireTimes, with support for fromUtc without modifying the trigger’s properties: ComputeFireTimes(ITrigger trigg, ICalendar? cal, DateTimeOffset fromUtc, int numTimes)

Thanks in advance for any insights!

Metadata

Metadata

Assignees

No one assigned

    Labels

    3.xCandidate for 3.x brancharea/triggersTrigger state management relatedbugConfirmed or reported bugs

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions