Introduction
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.
Background
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 CAlarmClock
class:
- 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.
#include "AlarmClock.h"
. - Add the following files to your project: "AlarmClock.cpp" and "AlarmClock.h".
The 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, FILETIME
(local), 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 FALSE
. 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 SetRepeat()
after 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 IsArmed
). - 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 REPEAT_PARMS
structure. SetRepeat
and GetRepeat
are still supported for compatibility but using the new SetRepeat_xxx
functions (below) is recommended instead. SetRepeat_Interval()
replaces SetRepeat()
and allows the programmer to set a repeat alarm that is interval based: (days, hours, minutes, seconds and milliseconds). SetRepeat_Monthly()
replaces SetRepeat()
and allows the programmer to set a repeat alarm that is based on a day of the month. SetRepeat_Weekdays()
replaces 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.
SetRepeatUntil()
GetRepeatUntil()
Note: The 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 OnInitDialog
.
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.
New functionality:
- 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 FileTime
to 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 SYSTEMTIME
. - I added some new functions to eliminate the need to fill in the
REPEAT_PARMS
yourself. They are: SetRepeat_Interval
, SetRepeat_Monthy
, SetRepeat_Weekdays
and SetRepeatUntil
. Now you can simply call one of these to set up the repeat parameters and the CAlarmClock
class manages its own internal copy of the REPEAT_PARMS
. You can still use the SetRepeat
function as before if desired. - The
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 SetRepeat_Interval
.
History
- 07/12/05: Article submission
- 03/17/06: Added functionality and fixed some bugs