~aleteoryx/muditaos

71fa4c5578eb2ea57899e63c3aa5c42461502483 — Paweł Joński 4 years ago 6e0c484
[BH-721] Fix and add new rrule generator methods

Add rrule generator for next incoming timestamp
Add ical iterator wrapper
Fix existing range generator to include event start timestamp
Use time lib TimePoint definition
Add neccessary public dependencies
M module-utils/rrule/CMakeLists.txt => module-utils/rrule/CMakeLists.txt +1 -0
@@ 13,6 13,7 @@ target_include_directories(rrule PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE
target_link_libraries(rrule
    PUBLIC
        date
        time
    PRIVATE
        ical_cxx
)

M module-utils/rrule/rrule/rrule.cpp => module-utils/rrule/rrule/rrule.cpp +78 -15
@@ 3,6 3,8 @@

#include "rrule.hpp"

#include <time/dateCommon.hpp>

extern "C"
{
#include <icalrecur.h>


@@ 22,13 24,44 @@ namespace rrule
    template <typename arrayT, typename vectT>
    unsigned vectorToIcalArray(const std::vector<vectT> &vect, arrayT *array, unsigned max_size);

    RRuleIter::RRuleIter(const RRule &rrule, const TimePoint eventStart)
    {
        auto icalEventStart = icaltime_from_timet_with_zone(std::chrono::system_clock::to_time_t(eventStart), 0, NULL);
        iter                = icalrecur_iterator_new(RRuletoIcalRecurrenceType(rrule), icalEventStart);
    }

    RRuleIter::~RRuleIter()
    {
        icalrecur_iterator_free(iter);
        iter = nullptr;
    }

    void RRuleIter::setRange(const TimePoint rangeStart, const TimePoint rangeEnd)
    {
        auto icalTimeStart = icaltime_from_timet_with_zone(std::chrono::system_clock::to_time_t(rangeStart), 0, NULL);
        auto icalTimeEnd   = icaltime_from_timet_with_zone(std::chrono::system_clock::to_time_t(rangeEnd), 0, NULL);

        icalrecur_iterator_set_range(iter, icalTimeStart, icalTimeEnd);
    }

    TimePoint RRuleIter::next()
    {
        icaltimetype nextIcal = icalrecur_iterator_next(iter);
        if (icaltime_is_null_time(nextIcal)) {
            return TIME_POINT_INVALID;
        }
        else {
            return std::chrono::system_clock::from_time_t(icaltime_as_timet(nextIcal));
        }
    }

    void RRule::parseFromString(std::string_view str)
    {
        const auto icalRrule    = icalrecurrencetype_from_string(str.data());
        const time_t untilTimeT = icaltime_as_timet(icalRrule.until);

        if (untilTimeT == 0) {
            until = TimePoint::min();
            until = TIME_POINT_INVALID;
        }
        else {
            until = std::chrono::system_clock::from_time_t(untilTimeT);


@@ 57,31 90,61 @@ namespace rrule
        return std::string{icalrecurrencetype_as_string(&icalRRule)};
    }

    std::vector<TimePoint> RRule::generateEventTimePoints(const TimePoint start,
                                                          const TimePoint end,
    std::vector<TimePoint> RRule::generateEventTimePoints(const TimePoint eventStart,
                                                          const TimePoint rangeStart,
                                                          const TimePoint rangeEnd,
                                                          const unsigned int count)
    {
        std::vector<TimePoint> eventsTimePoints;
        auto icalTimeStart   = icaltime_from_timet_with_zone(std::chrono::system_clock::to_time_t(start), 0, NULL);
        auto icalTimeEnd     = icaltime_from_timet_with_zone(std::chrono::system_clock::to_time_t(end), 0, NULL);
        unsigned int counter = 0;
        const icalrecurrencetype recur = RRuletoIcalRecurrenceType(*this);

        auto ritr = icalrecur_iterator_new(recur, icalTimeStart);
        icalrecur_iterator_set_range(ritr, icalTimeStart, icalTimeEnd);
        icaltimetype next = icalrecur_iterator_next(ritr);
        TimePoint singleEventTimePoint;
        auto rruleIter = RRuleIter(*this, eventStart);
        rruleIter.setRange(eventStart, rangeEnd);

        while (!icaltime_is_null_time(next) && counter < count) {
            auto singleEventTimePoint = std::chrono::system_clock::from_time_t(icaltime_as_timet(next));
            next                      = icalrecur_iterator_next(ritr);
        do {
            singleEventTimePoint = rruleIter.next();
            if (singleEventTimePoint < rangeStart) {
                continue;
            }
            eventsTimePoints.push_back(singleEventTimePoint);
            counter++;
        }
        } while (singleEventTimePoint != TIME_POINT_INVALID && singleEventTimePoint < rangeEnd && counter < count);

        icalrecur_iterator_free(ritr);
        return eventsTimePoints;
    }

    TimePoint RRule::generateNextTimePoint(const TimePoint eventStart, const TimePoint rangeStart)
    {
        TimePoint singleEventTimePoint;
        auto rruleIter = RRuleIter(*this, eventStart);

        do {
            singleEventTimePoint = rruleIter.next();

        } while (singleEventTimePoint != TIME_POINT_INVALID && singleEventTimePoint < rangeStart);

        return singleEventTimePoint;
    }

    TimePoint RRule::generateLastTimePoint(const TimePoint eventStart)
    {
        if (until == TIME_POINT_INVALID && count == 0) {
            return TIME_POINT_MAX;
        }

        auto lastValidEventTimePoint = TIME_POINT_MAX;
        auto singleEventTimePoint    = TIME_POINT_MAX;
        auto rruleIter               = RRuleIter(*this, eventStart);
        do {
            lastValidEventTimePoint = singleEventTimePoint;
            singleEventTimePoint    = rruleIter.next();

        } while (singleEventTimePoint != TIME_POINT_INVALID);

        return lastValidEventTimePoint;
    }

    icalrecurrencetype RRuletoIcalRecurrenceType(const RRule &rrule)
    {
        icalrecurrencetype icalRrule = ICALRECURRENCETYPE_INITIALIZER;


@@ 89,7 152,7 @@ namespace rrule
        icalRrule.freq  = RRuleFrequencyToIcalFrequency(rrule.freq);
        icalRrule.count = rrule.count;

        if (rrule.until != TimePoint::min()) {
        if (rrule.until != TIME_POINT_INVALID) {
            icalRrule.until = icaltime_from_timet_with_zone(std::chrono::system_clock::to_time_t(rrule.until), 0, NULL);
        }
        icalRrule.interval   = rrule.interval;

M module-utils/rrule/rrule/rrule.hpp => module-utils/rrule/rrule/rrule.hpp +24 -7
@@ 1,16 1,17 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include <chrono>
#include <time/dateCommon.hpp>

#include <cstdint>
#include <string>
#include <vector>

struct icalrecur_iterator_impl;
typedef struct icalrecur_iterator_impl icalrecur_iterator;

namespace rrule
{

    using TimePoint = std::chrono::time_point<std::chrono::system_clock>;

    class RRule
    {



@@ 41,7 42,7 @@ namespace rrule
        RRuleFrequency freq{RRuleFrequency::NO_RECURRENCE};
        /* until and count are mutually exclusive. */
        std::uint32_t count{0};
        TimePoint until{TimePoint::min()};
        TimePoint until{TIME_POINT_INVALID};
        std::uint16_t interval{0};
        RRuleWeekday weekStart{RRuleWeekday::NO_WEEKDAY};



@@ 57,8 58,24 @@ namespace rrule

        void parseFromString(std::string_view str);
        std::string parseToString();
        std::vector<TimePoint> generateEventTimePoints(const TimePoint start,
                                                       const TimePoint end,
        std::vector<TimePoint> generateEventTimePoints(const TimePoint eventStart,
                                                       const TimePoint rangeStart,
                                                       const TimePoint rangeEnd,
                                                       const unsigned int count);
        TimePoint generateNextTimePoint(const TimePoint eventStart, const TimePoint rangeStart);
        TimePoint generateLastTimePoint(const TimePoint eventStart);
    };

    class RRuleIter
    {

        icalrecur_iterator *iter;

      public:
        RRuleIter(const RRule &rrule, const TimePoint eventStart);
        ~RRuleIter();

        void setRange(const TimePoint rangeStart, const TimePoint rangeEnd);
        TimePoint next();
    };
} // namespace rrule

M module-utils/rrule/test/unittest_rrule.cpp => module-utils/rrule/test/unittest_rrule.cpp +91 -11
@@ 33,7 33,7 @@ namespace rrule
                rrule.parseFromString("FREQ=DAILY;INTERVAL=1");
                REQUIRE(rrule.freq == RRule::RRuleFrequency::DAILY_RECURRENCE);
                REQUIRE(rrule.count == 0);
                REQUIRE(rrule.until == TimePoint::min());
                REQUIRE(rrule.until == TIME_POINT_INVALID);
                REQUIRE(rrule.interval == 1);
                REQUIRE(rrule.weekStart == RRule::RRuleWeekday::MONDAY_WEEKDAY);
                REQUIRE((rrule.bySecond.empty() && rrule.byMinute.empty() && rrule.byHour.empty() &&


@@ 47,7 47,7 @@ namespace rrule
                rrule.parseFromString("FREQ=MONTHLY;BYMONTHDAY=1;INTERVAL=1");
                REQUIRE(rrule.freq == RRule::RRuleFrequency::MONTHLY_RECURRENCE);
                REQUIRE(rrule.count == 0);
                REQUIRE(rrule.until == TimePoint::min());
                REQUIRE(rrule.until == TIME_POINT_INVALID);
                REQUIRE(rrule.interval == 1);
                REQUIRE(rrule.weekStart == RRule::RRuleWeekday::MONDAY_WEEKDAY);
                REQUIRE(rrule.byMonthDay.size() == 1);


@@ 76,7 76,7 @@ namespace rrule
                rrule.parseFromString("FREQ=MONTHLY;BYSETPOS=4;BYDAY=TU;INTERVAL=2;COUNT=10");
                REQUIRE(rrule.freq == RRule::RRuleFrequency::MONTHLY_RECURRENCE);
                REQUIRE(rrule.count == 10);
                REQUIRE(rrule.until == TimePoint::min());
                REQUIRE(rrule.until == TIME_POINT_INVALID);
                REQUIRE(rrule.interval == 2);
                REQUIRE(rrule.weekStart == RRule::RRuleWeekday::MONDAY_WEEKDAY);
                REQUIRE(rrule.byDay.size() == 1);


@@ 144,18 144,21 @@ namespace rrule
        SECTION("Generate timestamps")
        {
            RRule rrule;
            TimePoint start         = TimePointFromString("2020-01-01 12:00:00");
            TimePoint end           = TimePointFromString("2020-02-01 12:00:00");
            TimePoint eventStart = TimePointFromString("2020-01-01 12:00:00");
            TimePoint rangeStart = TimePointFromString("2020-01-01 12:00:00");
            TimePoint rangeEnd   = TimePointFromString("2020-02-01 12:00:00");

            const auto GENERATE_ALL = 99999;

            SECTION("Basic daily")
            {
                const auto teststring = "FREQ=DAILY";
                start                 = TimePointFromString("2020-01-01 12:00:00");
                end                   = TimePointFromString("2020-02-01 12:00:00");
                eventStart            = TimePointFromString("2020-01-01 12:00:00");
                rangeStart            = TimePointFromString("2020-01-01 12:00:00");
                rangeEnd              = TimePointFromString("2020-02-01 12:00:00");
                rrule.parseFromString(teststring);

                auto timestamps = rrule.generateEventTimePoints(start, end, GENERATE_ALL);
                auto timestamps = rrule.generateEventTimePoints(eventStart, rangeStart, rangeEnd, GENERATE_ALL);

                REQUIRE(timestamps.size() == 31);
                REQUIRE(timestamps[0] == TimePointFromString("2020-01-01 12:00:00"));


@@ 165,17 168,94 @@ namespace rrule
            SECTION("Basic hourly interval count")
            {
                const auto teststring = "FREQ=HOURLY;INTERVAL=2;COUNT=10";
                start                 = TimePointFromString("2020-01-01 12:00:00");
                end                   = TimePointFromString("2020-02-01 12:00:00");
                eventStart            = TimePointFromString("2020-01-01 12:00:00");
                rangeStart            = TimePointFromString("2020-01-01 12:00:00");
                rangeEnd              = TimePointFromString("2020-02-01 12:00:00");
                rrule.parseFromString(teststring);

                auto timestamps = rrule.generateEventTimePoints(start, end, GENERATE_ALL);
                auto timestamps = rrule.generateEventTimePoints(eventStart, rangeStart, rangeEnd, GENERATE_ALL);

                REQUIRE(timestamps.size() == 10);
                REQUIRE(timestamps[0] == TimePointFromString("2020-01-01 12:00:00"));
                REQUIRE(timestamps[1] == TimePointFromString("2020-01-01 14:00:00"));
                REQUIRE(timestamps[9] == TimePointFromString("2020-01-02 06:00:00"));
            }

            SECTION("Basic hourly interval after 5 hours")
            {
                const auto teststring = "FREQ=HOURLY;INTERVAL=2";
                eventStart            = TimePointFromString("2020-01-01 12:00:00");
                rangeStart            = TimePointFromString("2020-01-01 16:30:00");
                rangeEnd              = TimePointFromString("2020-01-01 21:30:00");
                rrule.parseFromString(teststring);

                auto timestamps = rrule.generateEventTimePoints(eventStart, rangeStart, rangeEnd, GENERATE_ALL);

                REQUIRE(timestamps.size() == 2);
                REQUIRE(timestamps[0] == TimePointFromString("2020-01-01 18:00:00"));
                REQUIRE(timestamps[1] == TimePointFromString("2020-01-01 20:00:00"));
            }

            SECTION("Generate Next daily")
            {
                const auto teststring = "FREQ=DAILY";
                eventStart            = TimePointFromString("2020-01-01 12:00:00");
                rangeStart            = TimePointFromString("2020-01-07 12:00:00");
                rrule.parseFromString(teststring);

                auto timestamp = rrule.generateNextTimePoint(eventStart, rangeStart);
                REQUIRE(timestamp == TimePointFromString("2020-01-07 12:00:00"));
            }

            SECTION("Generate Next hourly")
            {
                const auto teststring = "FREQ=HOURLY;INTERVAL=2;COUNT=10";
                eventStart            = TimePointFromString("2020-01-01 12:00:00");
                rangeStart            = TimePointFromString("2020-01-01 15:30:00");
                rrule.parseFromString(teststring);

                auto timestamp = rrule.generateNextTimePoint(eventStart, rangeStart);
                REQUIRE(timestamp == TimePointFromString("2020-01-01 16:00:00"));
            }
            SECTION("Generate Next daily 5 days later")
            {
                const auto teststring = "FREQ=DAILY;INTERVAL=5";
                eventStart            = TimePointFromString("2020-01-01 12:00:00");
                rangeStart            = TimePointFromString("2020-01-3 12:00:00");

                rrule.parseFromString(teststring);
                auto timestamp = rrule.generateNextTimePoint(eventStart, rangeStart);
                REQUIRE(timestamp == TimePointFromString("2020-01-06 12:00:00"));
            }
            SECTION("Generate Last daily")
            {
                const auto teststring = "FREQ=DAILY;COUNT=10";
                eventStart            = TimePointFromString("2020-01-01 12:00:00");
                rrule.parseFromString(teststring);

                auto timestamp = rrule.generateLastTimePoint(eventStart);
                REQUIRE(timestamp == TimePointFromString("2020-01-10 12:00:00"));
            }

            SECTION("Generate Last hourly")
            {
                const auto teststring = "FREQ=HOURLY;INTERVAL=2;COUNT=10";
                eventStart            = TimePointFromString("2020-01-01 12:00:00");
                rrule.parseFromString(teststring);

                auto timestamp = rrule.generateLastTimePoint(eventStart);
                REQUIRE(timestamp == TimePointFromString("2020-01-02 06:00:00"));
            }

            SECTION("Generate Last intinite")
            {
                const auto teststring = "FREQ=HOURLY;INTERVAL=2";
                eventStart            = TimePointFromString("2020-01-01 12:00:00");
                rrule.parseFromString(teststring);

                auto timestamp = rrule.generateLastTimePoint(eventStart);
                REQUIRE(timestamp == TIME_POINT_MAX);
            }
        }
    }
} // namespace rrule

M module-utils/time/CMakeLists.txt => module-utils/time/CMakeLists.txt +1 -1
@@ 26,12 26,12 @@ target_include_directories(time PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_

target_link_libraries(time 
    PRIVATE 
        module-utils
        time-constants
        utz::utz
    PUBLIC
        i18n
        log
        module-utils
        utf8
)


M module-utils/time/time/dateCommon.hpp => module-utils/time/time/dateCommon.hpp +1 -0
@@ 100,6 100,7 @@ enum class Repeat
};

inline constexpr TimePoint TIME_POINT_INVALID = date::sys_days{date::January / 1 / 1970};
inline constexpr TimePoint TIME_POINT_MAX     = date::sys_days{date::April / 11 / 2262};
inline constexpr uint32_t yearDigitsNumb = 4, monthDigitsNumb = 2, dayDigitsNumb = 2, HourDigitsNumb = 2,
                          MinDigitsNumb = 2, SecDigitsNumb = 2;