Acknowledgements
Thanks to Keith Rule for his MemDC
class. Thanks to all the CPians who offered feedback and code for the DateEdit and MiniCalendar projects I posted earlier.
About Date Recurrence Patterns
Many applications require the ability to schedule appointments, workshops or applications, etc. The ability to schedule these items in a recurrence pattern is a must when there are more than a few dates involved.
I have looked for source code to do this but I was not pleased with what I found. There are numerous solutions available (search Google), but the ones I looked at were either not worth the money asked, not written well, did not work correctly or were too limited.
The biggest technical issue with date recurrence patterns is that there are any number of possible patterns which each need to be programmed separately. Most recurrence pattern code (including my own) chooses a set of the most commonly used patterns and provides support for these. Also, they rarely provide support for combining various patterns into one (i.e., occurs on first, third and last Friday of each month). My implementation is based on MS Outlook and includes support for the 7 patterns in MS Outlook. Also, my implementation does not include support for combining patterns.
Technical problems aside, events rarely occur exactly along a specific defined pattern. There are often situations where an event is schedule for Friday, but is reschedule for Thursday. Also, holidays, vacation and other events interfere with and modify the timing of these events. The users I have worked with avoid using the MS Outlook recurrence patterns for precisely this reason. Instead, they schedule individual appointments for the various dates involved. This is often a tedious process and in some cases, highly error prone.
About this Implementation
As mentioned above, I based my implementation on MS Outlook. The way I approached this project was to provide the same level of support as MS Outlook as a basis for getting started. I then reviewed the various enhancements that would benefit the users.
I researched the ability to combine date patterns and the problem with this is not a technical one, but rather a question of user interface and usability. How would the UI need to look and work? Would the average user be able to understand it? Would I need to provide a secondary UI for advanced mode? If someone sets up a combined pattern, would others be able to understand it? I decided not to implement combing support because I was unable to adequately answer these questions. The engine which I developed should be able to easily accommodate combining, though assuming I (or anyone else) can satisfactorily answer these questions.
The next enhancement I reviewed was implementing support for skip patterns within the main recurrence pattern. Skip patterns would allow for special dates (i.e., holidays, vacations, etc.) to be automatically removed from the resulting pattern and an appropriate date automatically added into the pattern. An example of this would be "Skip Christmas and reschedule for the following Monday". I have implemented support in the core engine for this feature. However, I have not developed the UI to go with this as it was a low priority (per my users). I plan to add this in a later release.
I also reviewed giving the user finite control over the dates included in the pattern. This would allow the user to remove a specific date from the pattern and add specific dates back into the pattern. After working with my users, I felt that this was the most important ability to provide. My implementation includes support for this and the UI provides a simple means for the user to control the pattern.
Implementation Overview
The CFPSRecurringDates
class provides the core date pattern generator support. This is a utility class with no direct UI. Methods are provided for accessing the details of the pattern and modifying it. This class exposes a Serialize
function so that it can be easily incorporated into many projects. (You may need to implement support for persisting the configuration data to a database, though.)
The CPrShtDateRecur
, CPrPgDateRecurBasic
, and CPrPgDateRecurPreview
classes provide a user interface for manipulating the pattern engine. These classes require the IDD_DATE_RECUR_BASIC
, IDD_DATE_RECUR_PREVIEW
, and IDB_RECUR_IMAGES
resource objects.
Also, I use a mini calendar control and date edit/picker control I developed earlier. These projects are available from Code Project here and here.
Getting Started
To include support for recurring dates in your project:
-
Copy the .cpp and .h files listed below and include them in your project.
| FPSDatePickerCtrl.h |
| FPSDatePickerCtrl.cpp |
| FPSDateTimeButtonCtrl.h |
| FPSDateTimeButtonCtrl.cpp |
| FPSDateTimeCtrl.h |
| FPSDateTimeCtrl.cpp |
| FPSDateTimePopupCtrl.h |
| FPSDateTimePopupCtrl.cpp |
| FPSMiniCalendarCtrl.h |
| FPSMiniCalendarCtrl.cpp |
| FPSMiniCalendarListCtrl.h |
| FPSMiniCalendarListCtrl.cpp |
| FPSRecurringDates.h |
| FPSRecurringDates.cpp |
| MemDC.h |
| OleDateTimeEx.h |
| OleDateTimeEx.cpp |
| PrPgDateRecurPreview.h |
| PrPgDateRecurPreview.cpp |
| PrShtDateRecur.h |
| PrShtDateRecur.cpp |
| PrShtDateRecurBasic.h |
| PrShtDateRecurBasic.cpp |
-
Copy the IDD_DATE_RECUR_BASIC
, IDD_DATE_RECUR_PREVIEW
, IDB_RECUR_IMAGE
and IDB_DATEPICKER_BUTTON
resources from the DateRecur.rc file (in the demo project) into your project.
Implementation Details
CFPSRecurringDates
Core date recurrence engine.
IMPORTANT METHODS
GeneratePattern(CPtrList& List) | Call this function to execute the recurrence pattern and retrieve the result set.
If the date pattern is configured for NO end, this function will only generate the first 31 occurrences within the pattern. You will need to use one of the other GeneratePattern functions if this is not adequate. |
GeneratePattern(CPtrList& List, COleDateTime& dtEndBy) | Call this function to execute the recurrence pattern and retrieve the result set up-to a cut off date specified by the dtEndBy parameter. |
GeneratePattern(CPtrList& List, int iMaxOccurences) | Call this function to execute the recurrence pattern and retrieve the result set up-to a cut off # of occurrences. |
CleanupDateList(CPtrList &List) | The GeneratePattern function takes a CPtrList& parameter which it populates with pointers to COleDateTime objects. It is necessary to cleanup this list before the list is deconstructed. |
IsDateInPattern(COleDateTime& dtCheck) | Call this function to determine if the specified date exists in the pattern.
This version of the function is provided for low-volume checks (i.e., once or twice) but should not be used within a loop as it can be quite slow since it must regenerate the pattern on each call. |
IsDateInPattern(CPtrList& List, COleDateTime &dtCheck) | Call this function to determine if the specified date exists in the pattern previously generated and stored in List .
This version of the function is preferred for performance over the previous version. |
Ongoing Development
This project is still under development, however the code posted here has been tested and functions correctly to the best of my knowledge.
I am currently working on implementing support for skip patterns into the UI. Also, I have wrapped the core engine into a COM component and added support for database persistence. This work is being done to implement an ASP (and hopefully an ASP.NET) version of the UI. When I have completed the ASP (ASP.NET?) version of the project, I will post it to Code Project.
I am also working with the users of this component to determine what other features/enhancements need to be made. I do not know what this will reveal, but if there are significant changes I will post them to Code Project.
Notes
You are free to use this code in your own projects, both personal and professional. It can be used in freeware, shareware or commercial software without a license fee, etc. This code is provided AS-IS and may cause the universe to implode, so use with care. I assume no liability for the results of this implosion. All I ask is that you include my name in the credits for your app and that you leave the header comments intact within the source code.
Change History
April 20, 2002 | Thanks to Neville Franks for identifying an issue with how numeric values were being entered.
Also, thanks to Martin Bohring for pointing out some localization issues.
- Modified basic config dialog to filter input for numeric fields. This prevented a message box from being displayed due to
DDX_ functions mapping to an integer. - Modified
CFPSDateTimeCtrl to automatically set date format based on regional settings in Control Panel. - Added functions to
COleDateTimeEx function to retrieve the date/time formats set in regional settings in Control Panel and convert values to C style ones. - Modified method used to populate start/end time combo boxes to use the appropriate time format as set in Control Panel.
|
April 22, 2002 | Thanks to Michael A. Barnhart for suggesting a new monthly-type pattern.
- Added a new month pattern option. This option allows for [First|Second|Third|Fourth|Last] [Day|Weekday|Weekend day|Sunday|etc] of Every X months [plus|minus] X days
|
April 23, 2002 | Thanks to Michael A. Barnhart for identifying a bug involving duplicate entries in 2 combo boxes.
Thanks to Kwakkie for identifying a problem in the yearly pattern due to a localization issue (my laziness). Also, time combo boxes not populated correctly (again localization issue) and unable to use numeric keypad for numeric fields.
- Corrected problem w/duplicate entries in 2 combo boxes.
- Corrected problem w/yearly pattern caused by using
ParseDateTime instead of SetDate . - Corrected problem w/time combo boxes caused by incorrect functionality in the
ConvertVBFormatToCFormat function. - Corrected problem w/numeric fields not allowing numeric keypad entry.
|