diff --git a/include/hueplusplus/TimePattern.h b/include/hueplusplus/TimePattern.h index d1183a5..2017ebb 100644 --- a/include/hueplusplus/TimePattern.h +++ b/include/hueplusplus/TimePattern.h @@ -42,15 +42,16 @@ class AbsoluteTime public: explicit AbsoluteTime(clock::time_point baseTime, clock::duration variation = std::chrono::seconds(0)); - clock::time_point getBaseTime(); - clock::duration getRandomVariation(); + clock::time_point getBaseTime() const; + clock::duration getRandomVariation() const; - std::string toString(); + std::string toString() const; private: clock::time_point base; clock::duration variation; }; + class Weekdays { public: @@ -83,9 +84,14 @@ public: static Weekdays sunday(); static Weekdays parse(const std::string& s); + + bool operator==(const Weekdays& other) const { return bitmask == other.bitmask; } + bool operator!=(const Weekdays& other) const { return bitmask != other.bitmask; } + private: int bitmask; }; + class RecurringTime { using clock = std::chrono::system_clock; @@ -104,6 +110,7 @@ private: clock::duration variation; Weekdays days; }; + class TimeInterval { using clock = std::chrono::system_clock; @@ -122,6 +129,7 @@ private: clock::duration end; Weekdays days; }; + class Timer { using clock = std::chrono::system_clock; @@ -176,6 +184,8 @@ public: Timer asTimer() const; + std::string toString() const; + static TimePattern parse(const std::string& s); private: diff --git a/src/TimePattern.cpp b/src/TimePattern.cpp index b8c5cba..8ccc038 100644 --- a/src/TimePattern.cpp +++ b/src/TimePattern.cpp @@ -48,9 +48,9 @@ std::string timepointToTimestamp(clock::time_point time) clock::time_point parseTimestamp(const std::string& timestamp) { - std::tm tm; - tm.tm_year = std::stoi(timestamp.substr(0, 4)); - tm.tm_mon = std::stoi(timestamp.substr(5, 2)); + std::tm tm {}; + tm.tm_year = std::stoi(timestamp.substr(0, 4)) - 1900; + tm.tm_mon = std::stoi(timestamp.substr(5, 2)) - 1; tm.tm_mday = std::stoi(timestamp.substr(8, 2)); tm.tm_hour = std::stoi(timestamp.substr(11, 2)); tm.tm_min = std::stoi(timestamp.substr(14, 2)); @@ -82,21 +82,24 @@ std::string durationTo_hh_mm_ss(clock::duration duration) clock::duration parseDuration(const std::string& s) { using namespace std::chrono; - return hours(std::stoi(s.substr(0, 2))) + minutes(std::stoi(s.substr(3, 2))) + seconds(std::stoi(s.substr(7, 2))); + const hours hour(std::stoi(s.substr(0, 2))); + const minutes min(std::stoi(s.substr(3, 2))); + const seconds sec(std::stoi(s.substr(6, 2))); + return hour + min + sec; } AbsoluteTime::AbsoluteTime(clock::time_point baseTime, clock::duration variation) : base(baseTime), variation(variation) {} -clock::time_point AbsoluteTime::getBaseTime() +clock::time_point AbsoluteTime::getBaseTime() const { return base; } -clock::duration AbsoluteTime::getRandomVariation() +clock::duration AbsoluteTime::getRandomVariation() const { return variation; } -std::string AbsoluteTime::toString() +std::string AbsoluteTime::toString() const { std::string result = timepointToTimestamp(base); if (variation.count() != 0) @@ -246,14 +249,14 @@ std::string RecurringTime::toString() const { std::string result = "W"; result.append(days.toString()); - result.append("/"); + result.append("/T"); result.append(durationTo_hh_mm_ss(time)); if (variation.count() != 0) { result.push_back('A'); result.append(durationTo_hh_mm_ss(variation)); } - return std::string(); + return result; } TimeInterval::TimeInterval(clock::duration start, clock::duration end, Weekdays days) @@ -355,25 +358,13 @@ TimePattern::~TimePattern() destroy(); } -TimePattern::TimePattern(const AbsoluteTime& absolute) : type(Type::absolute) -{ - new (&this->absolute) AbsoluteTime(absolute); -} +TimePattern::TimePattern(const AbsoluteTime& absolute) : type(Type::absolute), absolute(absolute) {} -TimePattern::TimePattern(const RecurringTime& recurring) : type(Type::recurring) -{ - new (&this->recurring) RecurringTime(recurring); -} +TimePattern::TimePattern(const RecurringTime& recurring) : type(Type::recurring), recurring(recurring) {} -TimePattern::TimePattern(const TimeInterval& interval) : type(Type::interval) -{ - new (&this->interval) TimeInterval(interval); -} +TimePattern::TimePattern(const TimeInterval& interval) : type(Type::interval), interval(interval) {} -TimePattern::TimePattern(const Timer& timer) : type(Type::timer) -{ - new (&this->timer) Timer(timer); -} +TimePattern::TimePattern(const Timer& timer) : type(Type::timer), timer(timer) {} TimePattern::TimePattern(const TimePattern& other) : type(Type::undefined), undefined(nullptr) { @@ -444,6 +435,25 @@ Timer TimePattern::asTimer() const return timer; } +std::string TimePattern::toString() const +{ + switch (type) + { + case Type::undefined: + return std::string(); + case Type::absolute: + return absolute.toString(); + case Type::recurring: + return recurring.toString(); + case Type::interval: + return interval.toString(); + case Type::timer: + return timer.toString(); + default: + throw HueException(CURRENT_FILE_INFO, "TimePattern has wrong type"); + } +} + TimePattern TimePattern::parse(const std::string& s) { if (s.empty() || s == "none") @@ -471,7 +481,7 @@ TimePattern TimePattern::parse(const std::string& s) if (s.at(1) == '/') { // Infinite - numRepetitions = -1; + numRepetitions = 0; } else { @@ -484,7 +494,7 @@ TimePattern TimePattern::parse(const std::string& s) clock::duration variance = std::chrono::seconds(0); if (randomStart != std::string::npos) { - variance = parseDuration(s.substr(randomStart)); + variance = parseDuration(s.substr(randomStart+1)); } return TimePattern(Timer(expires, numRepetitions, variance)); } @@ -535,6 +545,8 @@ void TimePattern::destroy() timer.~Timer(); break; default: + // Do not throw exception, because it is called in destructor + // just ignore break; } type = Type::undefined; diff --git a/test/test_TimePattern.cpp b/test/test_TimePattern.cpp index 5aede3d..9b50dcf 100644 --- a/test/test_TimePattern.cpp +++ b/test/test_TimePattern.cpp @@ -26,6 +26,56 @@ using namespace hueplusplus::time; using std::chrono::system_clock; using namespace std::chrono_literals; +TEST(Time, parseTimestamp) +{ + std::tm tm {}; + tm.tm_year = 2020 - 1900; + tm.tm_mon = 3 - 1; + tm.tm_mday = 24; + tm.tm_hour = 12; + tm.tm_min = 45; + tm.tm_sec = 0; + // Auto detect daylight savings time + tm.tm_isdst = -1; + const std::time_t ctime = std::mktime(&tm); + const auto timePoint = system_clock::from_time_t(ctime); + EXPECT_EQ(timePoint, parseTimestamp("2020-03-24T12:45:00")); +} + +TEST(Time, timepointToTimestamp) +{ + std::tm tm {}; + tm.tm_year = 2020 - 1900; + tm.tm_mon = 3 - 1; + tm.tm_mday = 24; + tm.tm_hour = 12; + tm.tm_min = 45; + tm.tm_sec = 0; + // Auto detect daylight savings time + tm.tm_isdst = -1; + const std::time_t ctime = std::mktime(&tm); + const auto timePoint = system_clock::from_time_t(ctime); + EXPECT_EQ("2020-03-24T12:45:00", timepointToTimestamp(timePoint)); + + EXPECT_EQ(timePoint, parseTimestamp(timepointToTimestamp(timePoint))); +} + +TEST(Time, parseDuration) +{ + EXPECT_EQ(1h + 24min + 1s, parseDuration("01:24:01")); + EXPECT_EQ(22h + 59min + 49s, parseDuration("22:59:49")); + EXPECT_EQ(0s, parseDuration("00:00:00")); +} + +TEST(Time, durationTo_hh_mm_ss) +{ + EXPECT_EQ("00:00:00", durationTo_hh_mm_ss(0s)); + EXPECT_EQ("01:32:05", durationTo_hh_mm_ss(1h + 32min + 5s)); + EXPECT_EQ("20:45:13", durationTo_hh_mm_ss(20h + 45min + 13s)); + const auto duration = 20h + 45min + 13s; + EXPECT_EQ(duration, parseDuration(durationTo_hh_mm_ss(duration))); +} + TEST(AbsoluteTime, Constructor) { system_clock::time_point now = system_clock::now(); @@ -44,15 +94,7 @@ TEST(AbsoluteTime, Constructor) TEST(AbsoluteTime, toString) { - std::tm time {}; - time.tm_year = 2020 - 1900; - time.tm_mon = 2; - time.tm_mday = 3; - time.tm_hour = 20; - time.tm_min = 53; - time.tm_sec = 3; - std::time_t ctime = std::mktime(&time); - const system_clock::time_point timePoint = system_clock::from_time_t(ctime); + const system_clock::time_point timePoint = parseTimestamp("2020-03-03T20:53:03"); EXPECT_EQ("2020-03-03T20:53:03", AbsoluteTime(timePoint).toString()); @@ -122,9 +164,320 @@ TEST(Weekdays, unionWith) EXPECT_TRUE(day.isAll()); } +TEST(Weekdays, equals) +{ + EXPECT_EQ(Weekdays::monday(), Weekdays(0)); + EXPECT_EQ(Weekdays::none(), Weekdays()); + EXPECT_EQ(Weekdays::monday() | Weekdays::tuesday(), Weekdays::monday().unionWith(Weekdays::tuesday())); + + EXPECT_NE(Weekdays::none(), Weekdays(0)); + EXPECT_NE(Weekdays::all(), Weekdays::monday()); +} + TEST(Weekdays, toString) { EXPECT_EQ("001", Weekdays(0).toString()); EXPECT_EQ("064", Weekdays(6).toString()); EXPECT_EQ("112", (Weekdays(6) | Weekdays(5) | Weekdays(4)).toString()); } + +TEST(RecurringTime, Constructor) +{ + { + const auto time = 6h + 4min; + const Weekdays days = Weekdays::all(); + const RecurringTime recurring(time, days); + + EXPECT_EQ(time, recurring.getDaytime()); + EXPECT_EQ(0s, recurring.getRandomVariation()); + EXPECT_EQ(days, recurring.getWeekdays()); + } + { + const auto time = 2h + 3min + 2s; + const Weekdays days = Weekdays::monday() | Weekdays::friday(); + const auto variation = 40min; + const RecurringTime recurring(time, days, variation); + + EXPECT_EQ(time, recurring.getDaytime()); + EXPECT_EQ(variation, recurring.getRandomVariation()); + EXPECT_EQ(days, recurring.getWeekdays()); + } +} + +TEST(RecurringTime, toString) +{ + const auto time = 0h + 4min; + const RecurringTime recurring(time, Weekdays::monday()); + EXPECT_EQ("W001/T00:04:00", recurring.toString()); + + const RecurringTime variation(time, Weekdays::monday(), 1s); + EXPECT_EQ("W001/T00:04:00A00:00:01", variation.toString()); +} + +TEST(TimeInterval, Constructor) +{ + { + const auto start = 1h + 40min; + const auto end = 11h + 25s; + const TimeInterval interval(start, end); + + EXPECT_EQ(start, interval.getStartTime()); + EXPECT_EQ(end, interval.getEndTime()); + EXPECT_EQ(Weekdays::all(), interval.getWeekdays()); + } + { + const auto start = 0s; + const auto end = 20h; + const Weekdays days = Weekdays::friday() | Weekdays::saturday(); + const TimeInterval interval(start, end, days); + EXPECT_EQ(start, interval.getStartTime()); + EXPECT_EQ(end, interval.getEndTime()); + EXPECT_EQ(days, interval.getWeekdays()); + } +} + +TEST(TimeInterval, toString) +{ + { + const TimeInterval interval(1h + 40min, 11h + 25s); + EXPECT_EQ("T01:40:00/T11:00:25", interval.toString()); + } + { + const TimeInterval interval(0h, 20h + 1s, Weekdays::monday()); + EXPECT_EQ("W001/T00:00:00/T20:00:01", interval.toString()); + } +} + +TEST(Timer, Constructor) +{ + { + const auto duration = 1min + 20s; + const Timer timer(duration); + EXPECT_FALSE(timer.isRecurring()); + EXPECT_EQ(1, timer.getNumberOfExecutions()); + EXPECT_EQ(duration, timer.getExpiryTime()); + EXPECT_EQ(0s, timer.getRandomVariation()); + } + { + const auto duration = 1min + 20s; + const auto variation = 1h; + const Timer timer(duration, variation); + EXPECT_FALSE(timer.isRecurring()); + EXPECT_EQ(1, timer.getNumberOfExecutions()); + EXPECT_EQ(duration, timer.getExpiryTime()); + EXPECT_EQ(variation, timer.getRandomVariation()); + } + { + const auto duration = 1min + 20s; + const int num = 0; + const Timer timer(duration, num); + EXPECT_TRUE(timer.isRecurring()); + EXPECT_EQ(num, timer.getNumberOfExecutions()); + EXPECT_EQ(duration, timer.getExpiryTime()); + EXPECT_EQ(0s, timer.getRandomVariation()); + } + { + const auto duration = 1min + 20s; + const int num = 10; + const auto variation = 20min; + const Timer timer(duration, num, variation); + EXPECT_TRUE(timer.isRecurring()); + EXPECT_EQ(num, timer.getNumberOfExecutions()); + EXPECT_EQ(duration, timer.getExpiryTime()); + EXPECT_EQ(variation, timer.getRandomVariation()); + } +} + +TEST(Timer, toString) +{ + { + const Timer timer(1min + 20s); + EXPECT_EQ("PT00:01:20", timer.toString()); + } + { + const Timer timer(1min + 20s, 1h); + EXPECT_EQ("PT00:01:20A01:00:00", timer.toString()); + } + { + const Timer timer(1min + 20s, 0); + EXPECT_EQ("R/PT00:01:20", timer.toString()); + } + { + const Timer timer(1min + 20s, 1); + EXPECT_EQ("PT00:01:20", timer.toString()); + } + { + const Timer timer(1min + 20s, 15); + EXPECT_EQ("R15/PT00:01:20", timer.toString()); + } + { + const Timer timer(1min + 20s, 5, 1h); + EXPECT_EQ("R05/PT00:01:20A01:00:00", timer.toString()); + } + { + const Timer timer(1min + 20s, 0, 1h); + EXPECT_EQ("R/PT00:01:20A01:00:00", timer.toString()); + } +} + +TEST(TimePattern, Undefined) +{ + { + TimePattern pattern; + EXPECT_EQ(TimePattern::Type::undefined, pattern.getType()); + } + { + TimePattern pattern = TimePattern::parse(""); + EXPECT_EQ(TimePattern::Type::undefined, pattern.getType()); + } + { + TimePattern pattern = TimePattern::parse("none"); + EXPECT_EQ(TimePattern::Type::undefined, pattern.getType()); + } +} + +TEST(TimePattern, Absolute) +{ + { + const AbsoluteTime abs(system_clock::now(), 20s); + const TimePattern pattern(abs); + ASSERT_EQ(TimePattern::Type::absolute, pattern.getType()); + EXPECT_EQ(abs.getBaseTime(), pattern.asAbsolute().getBaseTime()); + EXPECT_EQ(abs.getRandomVariation(), pattern.asAbsolute().getRandomVariation()); + } + + const system_clock::time_point timePoint = parseTimestamp("2020-03-03T20:53:03"); + { + const TimePattern pattern = TimePattern::parse("2020-03-03T20:53:03"); + const AbsoluteTime expected(timePoint); + ASSERT_EQ(TimePattern::Type::absolute, pattern.getType()); + EXPECT_EQ(expected.getBaseTime(), pattern.asAbsolute().getBaseTime()); + EXPECT_EQ(expected.getRandomVariation(), pattern.asAbsolute().getRandomVariation()); + } + { + const system_clock::duration variation = 1h + 2min + 1s; + const TimePattern pattern = TimePattern::parse("2020-03-03T20:53:03A01:02:01"); + const AbsoluteTime expected(timePoint, variation); + ASSERT_EQ(TimePattern::Type::absolute, pattern.getType()); + EXPECT_EQ(expected.getBaseTime(), pattern.asAbsolute().getBaseTime()); + EXPECT_EQ(expected.getRandomVariation(), pattern.asAbsolute().getRandomVariation()); + } +} + +TEST(TimePattern, Recurring) +{ + { + const RecurringTime rec(12h + 30min, Weekdays::monday(), 1h); + const TimePattern pattern(rec); + ASSERT_EQ(TimePattern::Type::recurring, pattern.getType()); + EXPECT_EQ(rec.getDaytime(), pattern.asRecurring().getDaytime()); + EXPECT_EQ(rec.getWeekdays(), pattern.asRecurring().getWeekdays()); + EXPECT_EQ(rec.getRandomVariation(), pattern.asRecurring().getRandomVariation()); + } + { + const TimePattern pattern = TimePattern::parse("W001/T12:30:00"); + const RecurringTime expected(12h + 30min, Weekdays::monday()); + + ASSERT_EQ(TimePattern::Type::recurring, pattern.getType()); + EXPECT_EQ(expected.getDaytime(), pattern.asRecurring().getDaytime()); + EXPECT_EQ(expected.getWeekdays(), pattern.asRecurring().getWeekdays()); + EXPECT_EQ(expected.getRandomVariation(), pattern.asRecurring().getRandomVariation()); + } + { + const TimePattern pattern = TimePattern::parse("W001/T12:30:00A01:00:00"); + const RecurringTime expected(12h + 30min, Weekdays::monday(), 1h); + + ASSERT_EQ(TimePattern::Type::recurring, pattern.getType()); + EXPECT_EQ(expected.getDaytime(), pattern.asRecurring().getDaytime()); + EXPECT_EQ(expected.getWeekdays(), pattern.asRecurring().getWeekdays()); + EXPECT_EQ(expected.getRandomVariation(), pattern.asRecurring().getRandomVariation()); + } +} + +TEST(TimePattern, Interval) +{ + { + const TimeInterval interval(12h + 30min, 13h + 20min, Weekdays::friday()); + const TimePattern pattern(interval); + ASSERT_EQ(TimePattern::Type::interval, pattern.getType()); + EXPECT_EQ(interval.getStartTime(), pattern.asInterval().getStartTime()); + EXPECT_EQ(interval.getEndTime(), pattern.asInterval().getEndTime()); + EXPECT_EQ(interval.getWeekdays(), pattern.asInterval().getWeekdays()); + } + { + const TimeInterval expected(12h + 30min, 13h + 20min + 12s); + const TimePattern pattern = TimePattern::parse("T12:30:00/T13:20:12"); + ASSERT_EQ(TimePattern::Type::interval, pattern.getType()); + EXPECT_EQ(expected.getStartTime(), pattern.asInterval().getStartTime()); + EXPECT_EQ(expected.getEndTime(), pattern.asInterval().getEndTime()); + EXPECT_EQ(expected.getWeekdays(), pattern.asInterval().getWeekdays()); + } + { + const TimeInterval expected(12h + 30min, 13h + 20min + 12s, Weekdays::monday()); + const TimePattern pattern = TimePattern::parse("W001/T12:30:00/T13:20:12"); + ASSERT_EQ(TimePattern::Type::interval, pattern.getType()); + EXPECT_EQ(expected.getStartTime(), pattern.asInterval().getStartTime()); + EXPECT_EQ(expected.getEndTime(), pattern.asInterval().getEndTime()); + EXPECT_EQ(expected.getWeekdays(), pattern.asInterval().getWeekdays()); + } +} + +TEST(TimePattern, Timer) +{ + { + const Timer timer(1h + 30min, 5, 20s); + const TimePattern pattern(timer); + ASSERT_EQ(TimePattern::Type::timer, pattern.getType()); + EXPECT_EQ(timer.getExpiryTime(), pattern.asTimer().getExpiryTime()); + EXPECT_EQ(timer.getRandomVariation(), pattern.asTimer().getRandomVariation()); + EXPECT_EQ(timer.getNumberOfExecutions(), pattern.asTimer().getNumberOfExecutions()); + } + { + const Timer expected(1h + 30min + 20s); + const TimePattern pattern = TimePattern::parse("PT01:30:20"); + ASSERT_EQ(TimePattern::Type::timer, pattern.getType()); + EXPECT_EQ(expected.getExpiryTime(), pattern.asTimer().getExpiryTime()); + EXPECT_EQ(expected.getRandomVariation(), pattern.asTimer().getRandomVariation()); + EXPECT_EQ(expected.getNumberOfExecutions(), pattern.asTimer().getNumberOfExecutions()); + } + { + const Timer expected(1h + 30min + 20s, 20s); + const TimePattern pattern = TimePattern::parse("PT01:30:20A00:00:20"); + ASSERT_EQ(TimePattern::Type::timer, pattern.getType()); + EXPECT_EQ(expected.getExpiryTime(), pattern.asTimer().getExpiryTime()); + EXPECT_EQ(expected.getRandomVariation(), pattern.asTimer().getRandomVariation()); + EXPECT_EQ(expected.getNumberOfExecutions(), pattern.asTimer().getNumberOfExecutions()); + } + { + const Timer expected(1h + 30min + 20s, 0); + const TimePattern pattern = TimePattern::parse("R/PT01:30:20"); + ASSERT_EQ(TimePattern::Type::timer, pattern.getType()); + EXPECT_EQ(expected.getExpiryTime(), pattern.asTimer().getExpiryTime()); + EXPECT_EQ(expected.getRandomVariation(), pattern.asTimer().getRandomVariation()); + EXPECT_EQ(expected.getNumberOfExecutions(), pattern.asTimer().getNumberOfExecutions()); + } + { + const Timer expected(1h + 30min + 20s, 0, 20s); + const TimePattern pattern = TimePattern::parse("R/PT01:30:20A00:00:20"); + ASSERT_EQ(TimePattern::Type::timer, pattern.getType()); + EXPECT_EQ(expected.getExpiryTime(), pattern.asTimer().getExpiryTime()); + EXPECT_EQ(expected.getRandomVariation(), pattern.asTimer().getRandomVariation()); + EXPECT_EQ(expected.getNumberOfExecutions(), pattern.asTimer().getNumberOfExecutions()); + } + { + const Timer expected(1h + 30min + 20s, 5); + const TimePattern pattern = TimePattern::parse("R05/PT01:30:20"); + ASSERT_EQ(TimePattern::Type::timer, pattern.getType()); + EXPECT_EQ(expected.getExpiryTime(), pattern.asTimer().getExpiryTime()); + EXPECT_EQ(expected.getRandomVariation(), pattern.asTimer().getRandomVariation()); + EXPECT_EQ(expected.getNumberOfExecutions(), pattern.asTimer().getNumberOfExecutions()); + } + { + const Timer expected(1h + 30min + 20s, 5, 20s); + const TimePattern pattern = TimePattern::parse("R05/PT01:30:20A00:00:20"); + ASSERT_EQ(TimePattern::Type::timer, pattern.getType()); + EXPECT_EQ(expected.getExpiryTime(), pattern.asTimer().getExpiryTime()); + EXPECT_EQ(expected.getRandomVariation(), pattern.asTimer().getRandomVariation()); + EXPECT_EQ(expected.getNumberOfExecutions(), pattern.asTimer().getNumberOfExecutions()); + } +} \ No newline at end of file