.NET 6 introduced the PeriodicTimer.
I need to do something every minute, at the top of the minute. For example: 09:23:00, 09:24:00, 09:25:00, ...
But with a one minute period - new PeriodicTimer(TimeSpan.FromMinutes(1)) - and starting at 09:23:45, I will get "ticks" at: 09:24:45, 09:25:45, 09:26:45, ...
So it's dependent on the start time.
My workaround is a one-second period, and a check that the current time has seconds equal to 0. Another workaround is to wait for the next minute and then start the timer. Both approaches work but are fiddly and use too fine a resolution.
Is there a built-in or better way to trigger at the top of the minute rather than one-minute-after-start?
AFAIK there is nothing like this available in the standard .NET libraries. And I don't think that it's likely to be added any time soon. My suggestion is to use the third party Cronos library, that does a good job at calculating time intervals¹. You can find a usage example here, by Stephen Cleary. What this library does is to take a
DateTimeand a Cron expression as input, and calculate the nextDateTimethat satisfies this expression. It is just aDateTimecalculator, not a scheduler.If you want to get fancy you could include the functionality of the Cronos library in a custom
PeriodicTimer-like component, like the one below:Apart from the constructor, the
CronosPeriodicTimerclass has identical API and behavior with thePeriodicTimerclass. You could use it like this:The expression
0 * * * * *means "on the 0 (zero) second of every minute, of every hour, of every day of the month, of every month, and of every day of the week."You can find detailed documentation about the format of the Cron expressions here.
Caution: The
CronosPeriodicTimer.WaitForNextTickAsyncmethod calculates the interval until the next tick at the time it is invoked. In case the system clock is adjusted while aWaitForNextTickAsyncoperation is in-flight, the tick will not happen at the correct time according to the adjusted system time.The 500 milliseconds
minDelayhas the intention to prevent the remote possibility of the timer ticking twice by mistake. Also because thePeriodicTimerclass has a minimum period of 1 millisecond.The
TimeSpan periodparameter in thePeriodicTimer's constructor has an upper limit of approximately 49 days and 17 hours. In case the_cronExpression.GetNextOccurrencecall produces aDateTimefurther in the future than this, theCronosPeriodicTimer.WaitForNextTickAsynccall will fail with anArgumentOutOfRangeExceptionexception.For an implementation that uses the
Task.Delaymethod instead of thePeriodicTimerclass, and so it can be used by .NET versions previous than 6.0, you can look at the 3rd revision of this answer.¹ With the caveat that the Cronos library is currently capped to the year 2099 (version 0.7.1).