Had the same problem, except that it was a generic system that could do the same thing with completely adjustable intervals, from milliseconds to months. Well, was supposed to. Turned out it indeed messed up on intervals larger than 24.8 days because of this.
In my case, "rethinking the approach" was of out of the question, since it was just one small problem sub-case of a much larger Windows service system.
The solution was rather simple, but do note I had a side system that supplied me with the next execution as DateTime; the timer's job was just to match that to actually execute the task, so it calculated the interval simply by subtracting DateTime.Now from that.
In this case, all I needed to do was keep an overflow boolean alongside my timer object, and setting that when checking that interval against Int32.MaxValue:
private Timer _timer;
private Boolean _overflow;
private void QueueNextTime(DateTime thisTime)
{
TimeSpan interval = this.GetNextRunTime(thisTime) - DateTime.Now;
Int64 intervalInt = (Int64)((interval.TotalMilliseconds <= 0) ? 1 : interval.TotalMilliseconds);
// If interval greater than Int32.MaxValue, set the boolean to skip the next run. The interval will be topped at Int32.MaxValue.
// The TimerElapsed function will call this function again anyway, so no need to store any information on how much is left.
// It'll just repeat until the overflow status is 'false'.
this._overflow = intervalInt > Int32.MaxValue;
this._timer.Interval = Math.Min(intervalInt, Int32.MaxValue);
this._timer.Start();
}
// The function linked to _timer.Elapsed
private void TimerElapsed(object sender, ElapsedEventArgs e)
{
this._timer.Stop();
if (this._overflow)
{
QueueNextTime(e.SignalTime);
return;
}
// Execute tasks
// ...
// ...
// schedule next execution
QueueNextTime(e.SignalTime);
}
This is simplified, of course; the real system has try/catch and a system to abort on external stop commands. But that's the gist of it.