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:

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

Filecommon/interface/socket.h

Public functions IntToDays
IntToMonth

Classes TimeOccurrence
UTimeOccurrence

Data types recurring_days_t
recurring_month_t
RD_ALL_DAYS
RD_WEEK_DAYS
RD_WEEKEND_DAYS
RD_NO_END_DATE
RD_NO_LIMIT

Examples Code Example

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 daysA 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 monthA 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 startThe 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 daysA bitmask with the days for the occurences.
long64 startThe 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 dayNumThe number of the day.
recurring_days_t dayCan 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 startThe 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 dayNumThe number of the day.
recurring_days_t dayCan 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 monthOne of the recurring_month_t enums (except RM_NO_MONTH).
long64 startThe 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 weekDaysA 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 weekEndDaysA 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 indexThe 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 indexThe 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 * userThe 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 * timeOccurrenceThe calling TimeOccurrence instance.
long64 timeStampThe 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_DAYSA 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_DAYSA bitmask of recurring_days_t day enums, defining the week days (RD_MONDAY | RD_TUESDAY | RD_WEDNESDAY | RD_THURSDAY | RD_FRIDAY).
RD_WEEKEND_DAYSA bitmask of recurring_days_t day enums, defining the weekend days (RD_SUNDAY | RD_SATURDAY).
RD_NO_END_DATEUsed to define an series without an end date. Is set to INT64_MAX.
RD_NO_LIMITUsed 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_DAYUsed internally only, don't pass it to any function.
RD_SUNDAYThe enum for Sunday.
RD_MONDAYThe enum for Monday.
RD_TUESDAYThe enum for Tuesday.
RD_WEDNESDAYThe enum for Wednesday.
RD_THURSDAYThe enum for Thursday.
RD_FRIDAYThe enum for Friday.
RD_SATURDAYThe 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_JANUARYThe enum for January.
RM_FEBRUARYThe enum for february.
RM_MARCHThe enum for Marc.
RM_APRILThe enum for April.
RM_MAYThe enum for May.
RM_JUNEThe enum for June.
RM_JULYThe enum for July.
RM_AUGUSTThe enum for August.
RM_SEPTEMBERThe enum for September.
RM_OCTOBERThe enum for October.
RM_NOVEMBERThe enum for November.
RM_DECEMBERThe enum for December.
RM_NO_MONTHUsed 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);
}