I recently started working on a part of my application that requires a clock driven scheduler in order to carry out various tasks that the user needs at certain times of the day or week or month. So I decided that I'd try to make the clock calendar alarm component as portable as I can because I wanted to use it in two different apps. Since I tried to make this new class usable in at least two different programs, it wasn't much more effort to share it online. Hopefully others might be able to make use of it too.
This C++ class has the following functionality in order to allow the user to:
- Set an alarm that will asynchronously alert the application at a later time.
- Specify a repeating alarm using an arbitrary amount of time, every N days, certain days of the week, or a specific day every month.
- Have a guaranteed alarm accuracy of just a few milliseconds and have the same accuracy for all subsequent repeating alarms.
In order to measure time, the
CAlarmClock class uses the
FILETIME structure. It is supposedly accurate within 100 nanoseconds, but the computer hardware will not allow one to measure time units that small. The accuracy is actually much less than 100 nanoseconds, but whatever the accuracy is, it's only as accurate as the code that actually makes the time measurement. I'm not an expert at what the most accurate delay function available is from the various Windows APIs. I know of two ways to measure time: the
SetTimer() method that is very popular with me, and the Windows multimedia API function
timeSetEvent(). As far as I've read (in the docs)
timeSetEvent() is very accurate. I chose to use
timeSetEvent as the time base for the main timing loop.
Using the code
The Visual Studio project needs to have the following changes made to it in order to make use of the
- In Properties | Linker | Input | Additional Dependencies, add the library called "Winmm.lib" to the list for release and debug builds.
- Properties | C/C++ | Code Generation | Runtime Library should be set so that the program uses the multithreaded run time. The included sample application uses /MD and /MDd for release and debug builds, respectively.
- Add the following files to your project: "AlarmClock.cpp" and "AlarmClock.h".
CAlarmClock class has the following
public member functions:
SetAlarm() stores the caller's alarm date and time into the object's internal alarm variable and sets the alarm to the
ARMED state internally and starts timing. The caller's trigger will be delivered when the alarm date and time are reached. There are nine overloads for these functions. Three different input types are accepted for the time,
SYSTEMTIME (local) and integers. Three signal types are used in the various overloads: Callback, Window Message and Event.
SetAlarmAndWait() acts just like the
Sleep() function of Windows. It might not be suitable for long delays but it's there to make the object easier to use in some cases. There's no way to abort the alarm once it starts since the thread is tied up waiting for the alarm to be triggered. But another thread can call
Abort() if you really want to use it that way. I recommend not using
SetAlarmAndWait() except for the most basic timing loops that need a short delay. Also repeated alarms cannot be used with this function.
Abort() stops the current alarm and puts the object into a dormant state.
IsArmed() allows the caller to ask if the clock is set and armed, or whether it's idle and waiting for another
SetAlarm() to be issued.
IsArmed will return
TRUE if the alarm has been set but the alarm time hasn't been reached yet. After all alarm repeats have been triggered and exhausted,
IsArmed will eventually return
SetRepeat() provides the information needed to schedule one or more automatic repeats. See the
REPEAT_PARMS structure (in AlarmClock.h) for information on how to specify a repeated alarm event. There are strict requirements on how to properly set up the
REPEAT_PARMS in order to prevent the
CAlarmClock object from getting confused. Therefore,
SetRepeat() performs validation on the contents of the
REPEAT_PARMS parameter. Note: It is wise to call
SetRepeat() before calling
SetAlarm() but it is not required. I haven't tested whether calling
SetAlarm() works, but it is supposed to work as long as your alarm time doesn't expire first.
ResetRepeats() cancels any future repeat alarms before they occur. The next alarm that is already scheduled to be repeated will still occur when the alarm time is reached.
Abort() is the only way to cancel a scheduled alarm that hasn't occurred yet.
GetAlarmTime() allows you to retrieve the next alarm time from the object. The resulting
FILETIME contents are only valid if the object is in the
ARMED state (see
- New functions: The following
SetRepeat_xxx functions are a replacement for the
SetRepeat function. Using these new functions eliminates the need to know anything about the
GetRepeat are still supported for compatibility but using the new
SetRepeat_xxx functions (below) is recommended instead.
SetRepeat() and allows the programmer to set a repeat alarm that is interval based: (days, hours, minutes, seconds and milliseconds).
SetRepeat() and allows the programmer to set a repeat alarm that is based on a day of the month.
SetRepeat() and allows the programmer to set a repeat alarm that is one or more weekdays.
Note: All of the SetRepeat_xxx functions have a parameter
(long nCount) which indicates the number of repeats to use. Zero means repeat forever.
RepeatUntil function takes precedence over the repeat count and repeat forever setting. In other words, calling
RepeatUntil will cause the repeat count and repeat forever settings to be ignored. To replace the count, call
SetRepeat_xxxx again with a new count value or zero to indicate forever.
None of the functions in this class are virtualized. If you need to subclass, just declare the functions as
virtual in the base class if you want to. I just left it all as non-virtual since I have no reason to derive a class from this one myself yet.
If more than one alarm is needed, create more than one
CAlarmClock object and keep them all in an array if desired.
Points of interest
All time units are considered local, not UTC. If UTC is desired then you'll need to modify the class to deal with that format and you will need to convert the time to local if you want to know what the local time is. That brings up a point: if this calendar alarm works incorrectly in a different time zone than mine (PST -8) then please let me know so I can set up my dev machine to that zone and try to fix it. Or better yet, modify it and let me have the code so I can update the code in the article.
The class uses a worker thread to handle the time measurement. The worker thread will use the caller's choice (event, message or callback) to signal the caller during the alarm event. If you choose to use the callback function to handle the alarm event, you need to make sure that your callback function does not use much time so that the worker thread can resume measuring time. You should not call any of the member functions of the
CAlarmClock class from the callback function because this will cause a deadlock.
There's a bug in my version of Visual Studio .NET 2003 Pro that causes the
DateTimePicker control to keep switching back to its default type of date mode instead of time mode, every time I make any edits to the dialog box in the resource editor. Now the control will be updated at run time to force it to become the time format by altering the control's style bits in
The Repeat Forever flag in the
REPEAT_PARMS structure takes precedence over the repeat counter.
The test app generates a log file in the same path as the executable (or the startup path from the shortcut). The log file is there to help test the alarm repeats. It takes a long time to test a calendar alarm unit in real time so I had no choice but to take shortcuts in testing and make some assumptions regarding whether everything works. If you find any problems, please send me an email, or if you feel the problem is serious enough to tell everyone else then post the bug in the forum for this article if you wish.
- There is a new repeat end condition (in addition to "repeat forever" and "repeat n times"). It is "repeat until date". The field
tRepeatUntil inside the
REPEAT_PARMS represents the date/time you want the repeats to end. This field is a union called
UFT which makes it easy to convert from
ULONGLONG. One way to initialize this field is to use a
SYSTEMTIME structure as a template. Fill in the blanks then call
SystemTimeToFileTime and store the result in
REPEAT_PARMS::tRepeatUntil, or you can now call
SetRepeatUntil (new, discussed below) and pass it a pointer to the
- I added some new functions to eliminate the need to fill in the
REPEAT_PARMS yourself. They are:
SetRepeatUntil. Now you can simply call one of these to set up the repeat parameters and the
CAlarmClock class manages it's own internal copy of the
REPEAT_PARMS. You can still use the
SetRepeat function as before if desired.
REPEAT_PARMS was changed so that the
Repeat_Days mode was eliminated. To repeat for days, you now use the
Repeat_Interval mode and fill in the
REPEAT_PARMS::dd field (or call the equivalent function called
- Added functionality and fixed some bugs.