# Time occurrence

The time occurrence I library is for calculating recurring events 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, with the execption of adjusting the calculated time if it is not in the same DST as the start time (if the timezone of the start time had been defined). 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 if needed.

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

File | common/lib/time_occurrence.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 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 SetStartTimeTimeZone(class ITimeZone * tz);
        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

SetStartTimeTimeZone
  This function will set the timezone of the start time used for calculation. This is necessary to adjust the calculated result. The reason is DST (daylight saving time). If the start time is from a timezone with not DST, nothing must be done. However, if start is from a timezone with DST and is created during DST but the calculated result is on a da without DST, the calculated results must be adjusted. Same if start is not DST but the calculated result is. So that the app does not have to convert this itself, the timezone of that start time can be given so that the calculated result will be adjusted if needed.

  #### Parameters

  ITimeZone * tz | The ITimeZone instance to use. If nullptr, the adjustment of calculated result will be deactivated.

  #### Remarks

  Note that the calculated result will still be UTC. If you need it to be in local time of a specified timezone, that calculation must be done on your own. More information can be found in the ITimeZone documentation.
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);
    }