Time occurrence
The time occurrence I interface is for calculating time occurrences based on rules given by a recurring master.
This helps calculating time stamps for events like calendar appointments, scheduled events, and similar use cases.
The library doesn’t take care about time zones. It simply calculates the occurrences. So it is recommended to give
the start and end time for the recurring master in UTC time and transfer the calculated value to a specified time zone
using the ITimezone interface (which also takes care about daylight saving time).
An recurring master actually is a set of base information need to define a series and calculate calculate each occurrence.
This information will be the start time, and end time or a maximum number of occurrences (also no end time can be given to
have a never ending series), an intervall and the rule for the calculation. A rule can be one of the following options:
- Daily: This is for series that will occure each day.
- Weekly: This is for series that will occure weekly. A weekly series can occure on each day in a week. This will be defined by passing
a set of days. There are also predefined sets for week days and weekend days available.
- Monthly (absolute): This is for a monthly series with an absolute date inside the month (like every 15th day of a month).
- Monthly (relative): This is for monthly series with a relative day to occure (like every second Monday of a month or every fourth weekend day).
- Yearly (absolute): This is for series that will occure once a year at the given date.
- Yearlc (relative): This is for series that will occure once a year for a relative day for a month.
Note: yearly recurring masters (absolute and relative) technically are the same as monthly occurrences with an interval of 12 month. The difference is, that
the month for the occurrence can be defined for the yearly occurrence, while using a monthly occurrence with an interval of 12 will use the month of the
given start date
File information
Functions
Little helpers
inline recurring_days_t IntToDays(int days);
inline recurring_month_t IntToMonth(int month)
This functions are little helper functions to convert an integer value to a corresponding recurring_days_t or recurring_month_t enum. This helps when
getting the days or month ids from other sources like a database.
IntToDays
Parameters
int days | A day (or a set combined with or) to be converted to the recurring_days_t datatype. |
Return value
The value passed in recurring_days_t datatype. The value of the data is not changed. This only helps to make the compiler happy.
IntToMonth
Parameters
int month | A int defining a month to be converted to the recurring_days_t datatype. |
Return value
The value passed in recurring_month_t datatype. The value of the data is not changed. This only helps to make the compiler happy.
Classes
TimeOccurrence
class TimeOccurrence {
public:
TimeOccurrence();
~TimeOccurrence();
void SetDailyMaster(long64 start, long64 end = RD_NO_END_DATE, dword interval = 1, dword maxOccurrences = RD_NO_LIMIT);
void SetWeeklyMaster(dword days, long64 start, long64 end = RD_NO_END_DATE, dword interval = 1, dword maxOccurrences = RD_NO_LIMIT);
void SetMonthlyMaster(dword dayNum, recurring_days_t day, long64 start, long64 end = RD_NO_END_DATE, dword interval = 1, dword maxOccurrences = RD_NO_LIMIT);
void SetYearlyMaster(dword dayNum, recurring_days_t day, recurring_month_t month, long64 start, long64 end = RD_NO_END_DATE, dword interval = 1, dword maxOccurrences = RD_NO_LIMIT);
void SetWeekDays(word weekDays);
void SetWeekEndDays(word weekEndDays);
bool Empty() { return this->resultCount == 0; }
size_t Count() { return this->resultCount; }
long64 Result(size_t index);
long64 operator[] (size_t index) { return this->Result(index); }
void Calculate(long64 timePeriodStart = 0, long64 timePeriodEnd = RD_NO_END_DATE, dword maxResults = RD_NO_LIMIT);
void Calculate(UTimeOccurrence * user, long64 timePeriodStart = 0, long64 timePeriodEnd = RD_NO_END_DATE);
long64 CalculateFirstOccurrence(long64 timePeriodStart = 0);
long64 CalculateLastOccurrence(dword numOfOccurrences = 0);
};
The class that handles all the calculation. The result of the calculaten can be accessed by either a callback function (passing an UTimeOccurrence instance to the function
to start calculation), or by accessing the internal array of results (see below).
Public functions
SetDailyMaster
This function will set the recurring master as daily master for series that occure each day.
Parameters
long64 start | The timestamp to start the series. |
long64 end | (Default: RD_NO_END_DATE) The end date of the series. If RD_NO_END_DATE is given, the series won't end. |
dword interval | (Default: 1) The interval for the occurrences (e. g. 2 for every second day). |
dword maxOccurrences | (Default: RD_NO_LIMIT) The maximum occurrences than can happen for that series. |
Remarks
Using 0 as day will lead to an assertion. This also happens when setting an endDate AND the number of occurrences. Either one of them can be set. Note
that the function to calculate also takes a maximum of occurrences to be calculated.
SetWeeklyMaster
This function will set the recurring master as weekly master for series that occure at one or more days a week. Each day count as an occurrence. So if the
bitmask defines monday, wednesday and friday, the first occurrence will be monday, the second wednesday, the third friday, the fourth monday again and so on.
There are predefined daysets that can be used to define the days (RD_WEEK_DAYS and RD_WEEKEND_DAYS).
Parameters
dword days | A bitmask with the days for the occurences. |
long64 start | The timestamp to start the series. Will be adjusted to point to the first day given in days. |
long64 end | (Default: RD_NO_END_DATE) The end date of the series. If RD_NO_END_DATE is given, the series won't end. |
dword interval | (Default: 1) The interval for the occurrences (e. g. 2 for every second week). |
dword maxOccurrences | (Default: RD_NO_LIMIT) The maximum occurrences than can happen for that series. |
Remarks
Using 0 as day will lead to an assertion. This also happens when setting an endDate AND the number of occurrences. Either one of them can be set. Note
that the function to calculate also takes a maximum of occurrences to be calculated.
SetMonthlyMaster
This function will set the recurring master as monthly master. Monthly masters can be used as absolute or relative recurrence. Absolute means passing RD_DAY as day, which leads
to the day with the number given in dayNum will be the occurrence (e. g. 14th day of the month). Relative means to pass every other value from reccurring_days_t, which will
calculate the occurrence for the number of the day with the given name (e. G. 3rd monday). If the result lies outside of the current month, the last valid day will be used.
So asking for the 10th monday will always calculate the date of the last monday of the month. Note that in case of RD_WEEK_DAY, only sunday to friday will be used for the
calculation, while for RD_WEEKEND_DAY, saturday and sunday will be used. However, it also can be changed what day to count as week day and weekend day. See
SetWeekDays and SetWeekEndDays for details.
Parameters
dword dayNum | The number of the day. |
recurring_days_t day | Can be RD_DAY (for absolute recurring master), RD_WEEK_DAY, RD_WEEKANDDAY or one of the day enums
from RD_SUNDAY to RD_SATURDAY. |
long64 start | The timestamp to start the series. Will be adjusted to point to the first day given in day if. |
long64 end | (Default: RD_NO_END_DATE) The end date of the series. If RD_NO_END_DATE is given, the series won't end. |
dword interval | (Default: 1) The interval for the occurrences (e. g. 2 for every second month). |
dword maxOccurrences | (Default: RD_NO_LIMIT) The maximum occurrences than can happen for that series. |
Remarks
Using 0 as day will lead to an assertion. This also happens when setting an endDate AND the number of occurrences. Either one of them can be set. Note
that the function to calculate also takes a maximum of occurrences to be calculated.
SetYearlyMaster
This function will set the recurring master as yearly master. This will lead to only one occurence per year. Like a monthly master, a yearly one can be absolute or relative.
Absolute means passing RD_DAY as day, which leads to day given in dayNum at the month given in month (so passing 12 as dayNum and RD_APRIL as month will calculate the 12th of April
for a year). Relative means to pass every other value from reccurring_days_t, which will calculate the occurrence for the number of the day with the given name (e. G. 3rd monday)
for the given month. If the result lies outside of the current month, the last valid day will be used. So asking for the 10th monday for month set to RD_JANUARY will always calculate
the date of the last monday in January. Note that in case of RD_WEEK_DAY, only sunday to friday will be used for the calculation, while for RD_WEEKEND_DAY, saturday and sunday will
be used. However, it also can be changed what day to count as week day and weekend day. See SetWeekDays and SetWeekEndDays for details.
Parameters
dword dayNum | The number of the day. |
recurring_days_t day | Can be RD_DAY (for absolute recurring master), RD_WEEK_DAY, RD_WEEKANDDAY or one of the day enums
from RD_SUNDAY to RD_SATURDAY. |
recurring_month_t month | One of the recurring_month_t enums (except RM_NO_MONTH). |
long64 start | The timestamp to start the series. Will be adjusted to point to the first day given in day if. |
long64 end | (Default: RD_NO_END_DATE) The end date of the series. If RD_NO_END_DATE is given, the series won't end. |
dword interval | (Default: 1) The interval for the occurrences (e. g. 2 for every second week). |
dword maxOccurrences | (Default: RD_NO_LIMIT) The maximum occurrences than can happen for that series. |
Remarks
Using 0 as day will lead to an assertion. This also happens when setting an endDate AND the number of occurrences. Either one of them can be set. Note
that the function to calculate also takes a maximum of occurrences to be calculated.
SetWeekDays
Will be used to define the days that count as week days. The value given here will be used by a monthly or yeary recurring master to calculate the occurrences, if
RD_WEEK_DAY will be used as day.
Parameters
word weekDays | A bit mask of the recurring_days_t enums from RD_SUNDAY to RD_SATURDAY. |
Remarks
The default for the defined weekdays are the days Monday to Friday.
SetWeekendDays
Will be used to define the days that count as weekend days. The value given here will be used by a monthly or yeary recurring master to calculate the occurrences, if
RD_WEEKEND_DAY will be used as day.
Parameters
word weekEndDays | A bit mask of the recurring_days_t enums from RD_SUNDAY to RD_SATURDAY. |
Remarks
The default for the defined weekdays are RD_SUNDAY and RD_SATURDAY.
Empty
Return Value
True, if the internal result array is empty, else false.
Remarks
Should only be used when calculating occurrences without an UTimeOccurrence instance (see below).
Count
Return Value
Returns the number of items inside the internal result array.
Remarks
Should only be used when calculating occurrences without an UTimeOccurrence instance (see below).
Result
Must be called to receive a result of the calculation from the internal array.
Parameters
size_t index | The index of the result to get. Must ba a value between 0 and Count() - 1. |
Return Value
The timestamp of the calculated occurrence for the given index.
Remarks
Should only be used when calculating occurrences without an UTimeOccurrence instance (see below).
operator []
Can be used to access the internal result array as an normal C array.
Parameters
size_t index | The index of the result to get. Must ba a value between 0 and Count() - 1. |
Return Value
The timestamp of the calculated occurrence for the given index.
Remarks
Should only be used when calculating occurrences without an UTimeOccurrence instance (see below).
Calculate (overloaded)
Starts the calculation by using the defined recurring master. So one of the Set*Master() functions must be called first! This version of the
function calculates the requested number of occurrences and stores it inside an internal array. So be aware of how much occurrences you
want to calculate, because it can easily eat up a lot of memory and also block the app, because the function will return after every calculation
had been done. Use the oder Calculate() function that uses an UTimeOccurrence instance to have better control over the calculation (see below).
Parameters
long64 timePeriodStart | (Default 0) The start of time period, for wich the occurrences should be calculated. If 0, the
start time set with the recurring master will be used. |
long64 timePeriodEnd | (Default RD_NO_END_DATE) The end of the time period, for wich the occurrences should be calculated. If
RD_NO_END_DATE, the end date of the recurring master or the number of occurrences
will be used (depending on what had been defined while setting the recurring master). |
word maxResults | (Default RD_NO_LIMIT) The number of occurrences to calculate. If the end of the series is reached (either by an end date or a
number of occurrences defined through the recurring master), this end has priority. If set to RD_NO_LIMIT,
the end of the series defined by the recurring master will end the calculation. |
Remarks
WARNING: When calling this function with timePeriodEnd set to RD_NO_END_DATE and maxResults set to RD_NO_LIMIT, using a recurring master with no end date and no numbered limit
for occurrences, this function will never return becaus staying in an endless loop, eating up memory without end and consuming 100% of CPU time. With great power comes great responsibility.
Calculate (overloaded)
Starts the calculation by using the defined recurring master. So one of the Set*Master() functions must be called first! This version function calculates a occurrences
and calls back the given user passing the result before continuing calculating the next occurrence. Instead of the other Calculate() function, this one does not store the
values inside the internal array. On the other hand, the user called can cancel the calculation before the end has reached.
Parameters
UTimeOccurrence * user | The user to call back for each calculated occurrence. |
long64 timePeriodStart | (Default 0) The start of time period, for wich the occurrences should be calculated. If 0, the
start time set with the recurring master will be used. |
long64 timePeriodEnd | (Default RD_NO_END_DATE) The end of the time period, for wich the occurrences should be calculated. If
RD_NO_END_DATE, the end date of the recurring master or the number of occurrences
will be used (depending on what had been defined while setting the recurring master). |
CalculateFirstOccurrence
Calculates the first occurrence for the recurring master, starting from the given timePeriodStart. If the recurring master is set for a daily series, the return value
will be timePeriodStart or the start of the series, if timePeriodStart is 0. For every other recurring master type, the result will be the first occurrence that
combines with the defined rule from the given timePeriodStart on the start of the series (when timePeriodStart is 0).
Parameters
long64 timePeriodStart | (Default 0) The start of time period, for wich the first occurrenc should be calculated. If 0, the
start time set with the recurring master will be used. |
Return Value
The first occurrence of the series.
CalculateLastOccurrence
Calculates the last occurrence for the series. The calculation will be done in a direct way without iterrating through all the occurrences. So if the end of the series
is needed, this function will be faster than calling Calculate() until the end of the series had been reached.
Parameters
long64 timePeriodStart | (Default 0) The start of time period, for wich the first occurrenc should be calculated. If 0, the
start time set with the recurring master will be used. |
Return Value
The end of the series.
Remarks
The end of the series can be either the occurrence with the number given in numOfOccurrences, the occurrence for the end date of the series (if defined by the recurring master)
or the occurrence with the number of the maximum occurrences to calculate (again, if defined by the recurring master). If the series ends before the number of occurrences given
in numOfOccurrences had been reached, that last occurence will be returned. So there is no guarantee, that the requested quantity will be calculated. If numOfOccurrences is 0,
the series end date is set to RD_NO_END and the number of maxOccurrences for the series is RD_NO_LIMIT, the function will return RD_NO_END.
UTimeOccurrence
class UTimeOccurrence {
public:
UTimeOccurrence() {}
virtual ~UTimeOccurrence() {}
virtual bool TimeOccurrenceResult(class TimeOccurrence * timeOccurrence, long64 timeStamp) { return false; }
};
The User can be passed to one of the TimeOccurrence::Calculat() function. For each occurrence calculated, UTimeOccurrence::TimeOccurrenceResult() will be
called. The function should made what ever needed with the given timestamp and decide, whether to continue the calculation or not by returning ture or false.
Public functions
Parameters
TimeOccurrenceResult
Parameters
TimeOccurrence * timeOccurrence | The calling TimeOccurrence instance. |
long64 timeStamp | The time stamp for the calculated time occurrence. |
Return Value
The function must return true, if the next occurrence should be calculated, or false, to cancel calculation. Note that returning true doesn't mean,
that the calculation will continue. If the end of the series had been reached (defined by the end date or the maxOccurrence value) or if the number
of requested occurrences had been calculated, the calculation stops even if the function retruns true;
Data types
Defines / Statics
RD_ALL_DAYS | A bitmask combination of all recurring_days_t day enums (RD_SUNDAY | RD_MONDAY | RD_TUESDAY | RD_WEDNESDAY | RD_THURSDAY | RD_FRIDAY | RD_SATURDAY) |
RD_WEEK_DAYS | A bitmask of recurring_days_t day enums, defining the week days (RD_MONDAY | RD_TUESDAY | RD_WEDNESDAY | RD_THURSDAY | RD_FRIDAY). |
RD_WEEKEND_DAYS | A bitmask of recurring_days_t day enums, defining the weekend days (RD_SUNDAY | RD_SATURDAY). |
RD_NO_END_DATE | Used to define an series without an end date. Is set to INT64_MAX. |
RD_NO_LIMIT | Used to define that a series has no numbered limit. Is set to INT64_MAX. |
recurring_days_t
typedef enum {
RD_NO_DAY = 0x0000,
RD_SUNDAY = 0x0001,
RD_MONDAY = 0x0002,
RD_TUESDAY = 0x0004,
RD_WEDNESDAY = 0x0008,
RD_THURSDAY = 0x0010,
RD_FRIDAY = 0x0020,
RD_SATURDAY = 0x0040,
// The recurring types can also day, week day or weekend day (like: 3rd day of month, 2nd weekday our 1st weekend day)
RD_DAY = 0x0080,
RD_WEEK_DAY = 0x0100,
RD_WEEKEND_DAY = 0x0200
} recurring_days_t;
These enums are used to define the days needed for the recurring masters.
Values
RD_NO_DAY | Used internally only, don't pass it to any function. |
RD_SUNDAY | The enum for Sunday. |
RD_MONDAY | The enum for Monday. |
RD_TUESDAY | The enum for Tuesday. |
RD_WEDNESDAY | The enum for Wednesday. |
RD_THURSDAY | The enum for Thursday. |
RD_FRIDAY | The enum for Friday. |
RD_SATURDAY | The enum for Saturday. |
RD_DAY | (Use only for TimeOccurrence::SetMonthlyMaster and TimeOccurrence::SetYearlyMaster): Used to define the monthly or yearly recurring master to be absolute. |
RD_WEEK_DAY | (Use only for TimeOccurrence::SetMonthlyMaster and TimeOccurrence::SetYearlyMaster): Used to define the monthly or yearly calculate weekdays only. Weekdays can be define with TimeOccurrence::SetWeekDays(). |
RD_WEEKEND_DAY | (Use only for TimeOccurrence::SetMonthlyMaster and TimeOccurrence::SetYearlyMaster): Used to define the monthly or yearly calculate weekenddays only. Weekenddays can be define with TimeOccurrence::SetWeekendDays(). |
recurring_month_t
typedef enum {
RM_JANUARY,
RM_FEBRURARY,
RM_MARCH,
RM_APRIL,
RM_MAY,
RM_JUNE,
RM_JULY,
RM_AUGUST,
RM_SEPTEMBER,
RM_OCTOBER,
RM_NOVEMBER,
RM_DECEMBER,
RM_NO_MONTH = 0xFFFF
} recurring_month_t;
These enums are used to define the month for TimeOccurrence::SetMonthlyMaster() and TimeOccurrence::SetYearlyMaster().
Values
RM_JANUARY | The enum for January. |
RM_FEBRUARY | The enum for february. |
RM_MARCH | The enum for Marc. |
RM_APRIL | The enum for April. |
RM_MAY | The enum for May. |
RM_JUNE | The enum for June. |
RM_JULY | The enum for July. |
RM_AUGUST | The enum for August. |
RM_SEPTEMBER | The enum for September. |
RM_OCTOBER | The enum for October. |
RM_NOVEMBER | The enum for November. |
RM_DECEMBER | The enum for December. |
RM_NO_MONTH | Used internally only, don't pass it to any function. |
Code examples
constexpr long64 THREE_DAYS_MS = 259200000;
char buf[30];
TimeOccurrence timeOccurrence;
// Let' sassume the returned date will be Thuesday, the 5th of March 2019.
long64 t = ITime::TimeStampMilliseconds();
ITime::FormatTimeStampISO(buf, sizeof(buf), t);
debug->printf("Now: %s", buf); // Now: 2019-03-05T14:00:00
timeOccurrence.SetWeeklyMaster(RD_MONDAY | RD_THURSDAY, t - THREE_DAYS_MS);
timeOccurrence.Calculate(t, RD_NO_END_DATE, 3);
// The loop will produce the following output:
//
// Occurrence #1: 2019-03-07T14:00:00
// Occurrence #2: 2019-03-11T14:00:00
// Occurrence #3: 2019-03-14T14:00:00
//
for (int i = 0; i < timeOccurrence.Count(); ++i) {
ITime::FormatTimeStampISO(buf, sizeof(buf), timeOccurrence[i]);
debug->printf("Occurrence #%i: %s", i + 1, buf);
}