We have a WinForms program doing DataWarehouse report functions and need to include the option of firing off events at pretty long intervals (hours, and even days between them) and want to be able to tell the user that the app is still running with a bit of detail. I was thinking of showing a countdown in a label but the DateAdd functions are confusing me. Do you have any pointers?

What you're looking for isn't all that difficult, depending on the accuracy you require. I do have to say that you might want to look into serverside timing and having the desktops check the status of reports rather than kicking off the processing from a user GUI; Having a server manage the timing is probably going to be a little safer than asking users to keep their machines on for days at a stretch ... but that's up to you.

Even if you do rethink the architecture of this project, the trick you're looking for can be encapsulated in a class and used any time you want to have a repeated countdown. There are some things to consider however, so always talk to your PM or users and find out exactly what their needs are, not just what they think they want.

Dealing with time is always full of gotchas, it's like the fortune cookie said: "Man with one watch knows what time it is, man with two watches is never sure." There are so many variables that can impact a timer such as a user running a bunch of heavy apps .. and either playing with their system clock or having an automatic time syncher updating their clock automatically. Plus, there's the drift of the various timers you can pick from (System timer = up to 50+ms latency, QueryPerformanceCounter = reportedly around 10ms, Multimedia timer down to 1ms accuracy but a bit tough to implement).

DotNet2 has a cool new Stopwatch class that automatically picks a high performance counter if the OS has one (looks like it goes for the PerformanceCounter) and if not then it uses the System timer, so tick purists are getting a little more out of the abstraction layer, but till then we can keep things simple and get pretty close, perceptually anyway, by watchdogging the system timer.

Essentially this class encapsulates a timer object (not a Forms timer). You set the interval in the constructor using a TimeSpan and when the IsRunning property is set to True it starts the timer.

It's hard coded to tick at 1000ms so approximately every second the HandleTimer sub is called. That sub looks at the previously extrapolated m_NextFireEvent variable and if the current system time is equal to or later than that value then the FireEvent event is raised and the m_NextFireEvent variable is reset to the current system time plus the TimeSpan. Further, the sub calculates the days, hours, minutes and seconds between the current systemtime and the extrapolated next fire time and tosses that information up to the parent using the TMinus event.

Covering the base of a user playing with their system clock (or a corporate or downloaded clock setter running), when the timer is started we hook into the Win32.SystemEvents.TimeChanged event. If the event fires we set a module level flag that the timer will check in its' processing. We compare the last noted system datetime against the current system datetime and adjust the m_NextFireEvent accordingly. With these few lines we know that the timer interval will be correct relative to the last time it fired no matter what the system clock happens to say. Yes, this does impact the accuracy of the processing slightly, perhaps by a second or so becuase of the 1000ms click, but it's to account for fiddling outside the control of the app so the root cause is far more impactive than any minor deviation side effects of the recalculation.

To use the class, just add it to your project and attach it to your form with a WIthEvents declaration as in:

private WithEventsm_Countdowner as Countdowner

In the Class combo, pick the m_Countdowner and drop down the Method Name combo to get at events. Add your code to the TMinus event to update your labels and add code to the FireEvent event to start the timed actions.

Add a button to your form (or however you want to start the timer) and in its click add code to create a New instance (the constructor sets the TimeSpan) and set IsRunning to True.

You've noticed that I made the Interval property ReadOnly, which means that the only way to set the interval is by via the constructor, requiring the creation of a new instance. Feel free to add a setter if you need to after talking to your users about how their events are to be handled. Changing the Interval while the timer is running will mean recalculating the various internal properties and stopping and restarting the timer so you'll have to decide whether to trip the next pending event and then do the switch or dump the pending event and start anew from the current second ... maybe the choice should be dependant on how close you are to the next pending event (hmm, that's a nice bit of logic to ponder).