diff --git a/include/hueplusplus/Hue.h b/include/hueplusplus/Hue.h index e14efea..22d9f58 100644 --- a/include/hueplusplus/Hue.h +++ b/include/hueplusplus/Hue.h @@ -41,6 +41,7 @@ #include "json/json.hpp" +//! \brief Namespace for the hueplusplus library namespace hueplusplus { // forward declarations diff --git a/include/hueplusplus/TimePattern.h b/include/hueplusplus/TimePattern.h index 61b1cc8..e0aba17 100644 --- a/include/hueplusplus/TimePattern.h +++ b/include/hueplusplus/TimePattern.h @@ -27,26 +27,74 @@ namespace hueplusplus { +//! \brief Namespace for time/date related classes and functions namespace time { +//! \brief Converts a time_point to a timestamp string +//! \param time Time to convert +//! \returns Date and time in the format +//! YYYY-MM-DDThh:mm:ss. +//! +//! Returns the time in the local time zone. +//! \throws HueException when time could not be converted std::string timepointToTimestamp(std::chrono::system_clock::time_point time); + +//! \brief Converts a timestamp to a time_point +//! \param timestamp Timestamp from the local time zone in the format +//! YYYY-MM-DDThh:mm:ss +//! \returns time_point of the local system clock +//! \throws std::invalid_argument when integer conversion fails +//! \throws HueException when time cannot be represented as time_point std::chrono::system_clock::time_point parseTimestamp(const std::string& timestamp); +//! \brief Converts duration to a time string +//! \param duration Duration or time of day to format. Must be less than 24 hours +//! \returns Duration string in the format hh:mm:ss +//! \throws HueException when \c duration longer than 24 hours. std::string durationTo_hh_mm_ss(std::chrono::system_clock::duration duration); + +//! \brief Converts time string to a duration +//! \param hourMinSec Time/duration in the format hh:mm:ss +//! \returns Duration (hours, minutes and seconds) from the string +//! \throws std::invalid_argument when integer conversion fails std::chrono::system_clock::duration parseDuration(const std::string& hourMinSec); +//! \brief One-time, absolute time point with possible random variation +//! +//! Can be either used to represent a specific date and time, +//! or a date and time with a random variation. class AbsoluteTime { using clock = std::chrono::system_clock; public: + //! \brief Create absolute time point + //! \param baseTime Absolute time point + //! \param variation Random variation, optional. When not zero, the time is randomly chosen between + //! baseTime - variation and baseTime + variation explicit AbsoluteTime(clock::time_point baseTime, clock::duration variation = std::chrono::seconds(0)); + //! \brief Get base time point + //! + //! Can be used for calculation with other system_clock time_points clock::time_point getBaseTime() const; + + //! \brief Get random variation or zero + //! + //! The time can vary up to this amount in both directions. clock::duration getRandomVariation() const; + //! \brief Get formatted string as expected by Hue API + //! \returns when variation is 0: Timestamp in the format + //! YYYY-MM-DDThh:mm:ss + //! \returns when there is variation: Timestamp in the format + //! YYYY-MM-DDThh:mm:ssAhh:mm:ss + //! with base time first, variation second std::string toString() const; + //! \brief Parse AbsoluteTime from formatted string + //! \param s Timestamp in the same format as returned by \ref toString() + //! \returns AbsoluteTime with base time and variation from \c s static AbsoluteTime parse(const std::string& s); private: @@ -54,57 +102,113 @@ private: clock::duration variation; }; +//! \brief Any number of days of the week +//! +//! Can be used to represent weekly repetitions only on certain days. class Weekdays { public: + //! \brief Create with no days Weekdays() : bitmask(0) {} + //! \brief Create with the day \c num + //! \param num Day of the week, from monday (0) to sunday (6) explicit Weekdays(int num) : bitmask(1 << num) {} + //! \brief Check if no days are set bool isNone() const; + //! \brief Check if all days are set bool isAll() const; + //! \brief Check if Monday is contained bool isMonday() const; + //! \brief Check if Tuesday is contained bool isTuesday() const; + //! \brief Check if Wednesday is contained bool isWednesday() const; + //! \brief Check if Thursday is contained bool isThursday() const; + //! \brief Check if Friday is contained bool isFriday() const; + //! \brief Check if Saturday is contained bool isSaturday() const; + //! \brief Check if Sunday is contained bool isSunday() const; + //! \brief Create set union with other Weekdays + //! \param other Second set of days to combine with + //! \returns A set of days containing all days of either \c this or \c other Weekdays unionWith(Weekdays other) const; + //! \brief Create set union with other Weekdays + //! \see unionWith Weekdays operator|(Weekdays other) const { return unionWith(other); } + //! \brief Create a formatted, numeric string + //! \returns A three digit code for the days as a bitmask std::string toString() const; + //! \brief Creates an empty Weekdays static Weekdays none(); + //! \brief Creates set of all days static Weekdays all(); + //! \brief Creates Monday static Weekdays monday(); + //! \brief Creates Tuesday static Weekdays tuesday(); + //! \brief Creates Wednesday static Weekdays wednesday(); + //! \brief Creates Thursday static Weekdays thursday(); + //! \brief Creates Friday static Weekdays friday(); + //! \brief Creates Saturday static Weekdays saturday(); + //! \brief Creates Sunday static Weekdays sunday(); + //! \brief Parse from three digit code + //! \param s Bitmask of days as a string + //! \returns Parsed set of weekdays static Weekdays parse(const std::string& s); + //! \brief Check whether all days are equal bool operator==(const Weekdays& other) const { return bitmask == other.bitmask; } + //! \brief Check whether not all days are equal bool operator!=(const Weekdays& other) const { return bitmask != other.bitmask; } private: int bitmask; }; +//! \brief Time repeated weekly to daily, with possible random variation. +//! +//! Can be used to represent a time on one or multiple days per week. +//! It can also have a random variation of up to 12 hours. class RecurringTime { using clock = std::chrono::system_clock; public: + //! \brief Create recurring time + //! \param daytime Time of day, duration from the start of the day. + //! \param days Days to repeat on, should not be Weekdays::none() + //! \param variation Random variation, optional. Must be less than 12 hours. When not zero, the time is randomly + //! chosen between daytime - variation and daytime + variation explicit RecurringTime(clock::duration daytime, Weekdays days, clock::duration variation = std::chrono::seconds(0)); + //! \brief Get time of day clock::duration getDaytime() const; + //! \brief Get random variation + //! + //! The time can vary up to this amount in both directions. clock::duration getRandomVariation() const; + //! \brief Get days on which the repetition will happen Weekdays getWeekdays() const; + //! \brief Get formatted string as expected by Hue API + //! \returns with no variation: + //! Wbbb/Thh:mm:ss + //! \returns with variation: + //! Wbbb/Thh:mm:ssAhh:mm:ss, + //! where daytime is first and variation is second. std::string toString() const; private: @@ -113,17 +217,34 @@ private: Weekdays days; }; +//! \brief Time interval repeated daily to weekly. +//! +//! Can be used to represent an interval of time on one or multiple days per week. +//! The maximum interval length is 23 hours. class TimeInterval { using clock = std::chrono::system_clock; public: + //! \brief Create time interval + //! \param start Start time, duration from the start of the day + //! \param end End time, duration from the start of the day + //! \param days Active days, optional. Defaults to daily repetition. TimeInterval(clock::duration start, clock::duration end, Weekdays days = Weekdays::all()); + //! \brief Get start time of the interval clock::duration getStartTime() const; + //! \brief Get end time of the interval clock::duration getEndTime() const; + //! \brief Get active days Weekdays getWeekdays() const; + //! \brief Get formatted string as expected by Hue API + //! \returns with daily repetition: + //! Thh:mm:ss/Thh:mm:ss, + //! with start time first and end time second. + //! \returns with repetition that is not daily: + //! Wbbb/Thh:mm:ss/Thh:mm:ss std::string toString() const; private: @@ -132,19 +253,53 @@ private: Weekdays days; }; +//! \brief Timer that is started and triggers after specified delay +//! +//! The timer can have a random variation in the expiry time. +//! It can be one-off, repeated a set number of times or repeated indefinitely. class Timer { using clock = std::chrono::system_clock; public: + // \brief Used to represent infinite repetitions + static constexpr int infiniteExecutions = 0; + + //! \brief Create one-off timer + //! \param duration Expiry time of the timer, max 24 hours. + //! \param variation Random variation of expiry time, optional. Timer(clock::duration duration, clock::duration variation = std::chrono::seconds(0)); + //! \brief Create a repeated timer. + //! \param duration Expiry time of the timer, max 24 hours. + //! \param numExecutions Number of executions, 1 or higher, or \ref infiniteExecutions to always repeat. + //! \param variation Random variation of expiry time, optional. Timer(clock::duration duration, int numExecutions, clock::duration variation = std::chrono::seconds(0)); + //! \brief Returns true when the timer is executed more than once bool isRecurring() const; + + //! \brief Get number of executions + //! \returns Number of executions, or \ref infiniteExecutions int getNumberOfExecutions() const; + //! \brief Get expiry time clock::duration getExpiryTime() const; + //! \brief Get random variation of expiry time + //! + //! The expiry time can vary up to this value in both directions. clock::duration getRandomVariation() const; + //! \brief Get formatted string as expected by Hue API + //! \returns one-off timer: PThh:mm:ss + //! \returns one-off timer with variation: + //! PThh:mm:ssAhh:mm:ss, + //! with expiry time first and variation second. + //! \returns recurring timer: R/PThh:mm:ss + //! \returns recurring timer with n repetitions: + //! Rnn/PThh:mm:ss + //! \returns recurring timer with random variation: + //! Rnn/PThh:mm:ssAhh:mm:ss + //! \returns infinite recurring timer with random variation: + //! R/PThh:mm:ssAhh:mm:ss std::string toString() const; private: @@ -153,41 +308,75 @@ private: int numExecutions; }; +//! \brief Holds different time representations +//! +//! Holds either AbsoluteTime, RecurringTime, TimeInterval, Timer or an undefined state. +//! TimePattern is used to specify the occurrance of Schedule%s. class TimePattern { public: + //! \brief Currently active type enum class Type { - undefined, - absolute, - recurring, - interval, - timer + undefined, //!< \brief No active type + absolute, //!< \brief Active type is AbsoluteTime + recurring, //!< \brief Active type is RecurringTime + interval, //!< \brief Active type is TimeInterval + timer //!< \brief Active type is Timer }; + //! \brief Create empty TimePattern TimePattern(); + //! \brief Destructor for union. ~TimePattern(); + //! \brief Create TimePattern from AbsoluteTime explicit TimePattern(const AbsoluteTime& absolute); + //! \brief Create TimePattern from RecurringTime explicit TimePattern(const RecurringTime& recurring); + //! \brief Create TimePattern from TimeInterval explicit TimePattern(const TimeInterval& interval); + //! \brief Create TimePattern from Timer explicit TimePattern(const Timer& timer); + //! \brief Copy constructor for union TimePattern(const TimePattern& other); + //! \brief Copy assignment for union TimePattern& operator=(const TimePattern& other); + //! \brief Get currently active type + //! \note Only the currently active type may be accessed, + //! anything else is undefined behavior. Type getType() const; + //! \brief Get contained absolute time + //! \pre getType() == Type::absolute AbsoluteTime asAbsolute() const; + //! \brief Get contained recurring time + //! \pre getType() == Type::recurring RecurringTime asRecurring() const; + //! \brief Get contained time interval + //! \pre getType() == Type::interval TimeInterval asInterval() const; + //! \brief Get contained timer + //! \pre getType() == Type::timer Timer asTimer() const; + //! \brief Get formatted string of the contained value as expected by Hue API + //! \returns Empty string when type is undefined, otherwise toString() of the active type. + //! \see AbsoluteTime::toString, RecurringTime::toString, TimeInterval::toString, Timer::toString std::string toString() const; + //! \brief Parses TimePattern from formatted string as returned by Hue API + //! \param s Empty string, "none", or in one of the formats the contained types + //! return in their toString() method. + //! \returns TimePattern with the matching type that is given in \c s + //! \see AbsoluteTime::toString, RecurringTime::toString, TimeInterval::toString, Timer::toString + //! \throws HueException when the format does not match or a parsing error occurs + //! \throws std::invalid_argument when an integer conversion fails static TimePattern parse(const std::string& s); private: diff --git a/include/hueplusplus/Utils.h b/include/hueplusplus/Utils.h index 4c547d1..ed51703 100644 --- a/include/hueplusplus/Utils.h +++ b/include/hueplusplus/Utils.h @@ -27,6 +27,7 @@ namespace hueplusplus { +//! \brief Utility functions used in multiple places. namespace utils { namespace detail diff --git a/src/TimePattern.cpp b/src/TimePattern.cpp index c324651..ebd8d33 100644 --- a/src/TimePattern.cpp +++ b/src/TimePattern.cpp @@ -28,14 +28,19 @@ namespace hueplusplus { namespace time { - -using clock = std::chrono::system_clock; -std::string timepointToTimestamp(clock::time_point time) +using std::chrono::system_clock; +// Full name needed for doxygen +std::string timepointToTimestamp(std::chrono::system_clock::time_point time) { using namespace std::chrono; - std::time_t ctime = clock::to_time_t(time); + std::time_t ctime = system_clock::to_time_t(time); - std::tm localtime = *std::localtime(&ctime); + std::tm* pLocaltime = std::localtime(&ctime); + if (pLocaltime == nullptr) + { + throw HueException(CURRENT_FILE_INFO, "localtime failed"); + } + std::tm localtime = *pLocaltime; char buf[32]; std::size_t result = std::strftime(buf, sizeof(buf), "%FT%T", &localtime); @@ -46,7 +51,7 @@ std::string timepointToTimestamp(clock::time_point time) return std::string(buf); } -clock::time_point parseTimestamp(const std::string& timestamp) +system_clock::time_point parseTimestamp(const std::string& timestamp) { std::tm tm {}; tm.tm_year = std::stoi(timestamp.substr(0, 4)) - 1900; @@ -58,10 +63,15 @@ clock::time_point parseTimestamp(const std::string& timestamp) // Auto detect daylight savings time tm.tm_isdst = -1; std::time_t ctime = std::mktime(&tm); - return clock::from_time_t(ctime); + if (ctime == -1) + { + throw HueException(CURRENT_FILE_INFO, "mktime failed"); + } + return system_clock::from_time_t(ctime); } -std::string durationTo_hh_mm_ss(clock::duration duration) +// Full name needed for doxygen +std::string durationTo_hh_mm_ss(std::chrono::system_clock::duration duration) { using namespace std::chrono; if (duration > hours(24)) @@ -79,7 +89,7 @@ std::string durationTo_hh_mm_ss(clock::duration duration) return std::string(result); } -clock::duration parseDuration(const std::string& s) +system_clock::duration parseDuration(const std::string& s) { using namespace std::chrono; const hours hour(std::stoi(s.substr(0, 2))); @@ -91,11 +101,11 @@ clock::duration parseDuration(const std::string& s) AbsoluteTime::AbsoluteTime(clock::time_point baseTime, clock::duration variation) : base(baseTime), variation(variation) {} -clock::time_point AbsoluteTime::getBaseTime() const +system_clock::time_point AbsoluteTime::getBaseTime() const { return base; } -clock::duration AbsoluteTime::getRandomVariation() const +system_clock::duration AbsoluteTime::getRandomVariation() const { return variation; } @@ -243,12 +253,12 @@ RecurringTime::RecurringTime(clock::duration daytime, Weekdays days, clock::dura : time(daytime), days(days), variation(variation) {} -clock::duration RecurringTime::getDaytime() const +system_clock::duration RecurringTime::getDaytime() const { return time; } -clock::duration RecurringTime::getRandomVariation() const +system_clock::duration RecurringTime::getRandomVariation() const { return variation; } @@ -276,12 +286,12 @@ TimeInterval::TimeInterval(clock::duration start, clock::duration end, Weekdays : start(start), end(end), days(days) {} -clock::duration TimeInterval::getStartTime() const +system_clock::duration TimeInterval::getStartTime() const { return start; } -clock::duration TimeInterval::getEndTime() const +system_clock::duration TimeInterval::getEndTime() const { return end; } @@ -326,12 +336,12 @@ int Timer::getNumberOfExecutions() const return numExecutions; } -clock::duration Timer::getExpiryTime() const +system_clock::duration Timer::getExpiryTime() const { return expires; } -clock::duration Timer::getRandomVariation() const +system_clock::duration Timer::getRandomVariation() const { return variation; } @@ -342,7 +352,7 @@ std::string Timer::toString() const if (numExecutions != 1) { result.push_back('R'); - if (numExecutions != 0) + if (numExecutions != infiniteExecutions) { std::string s = std::to_string(numExecutions); // Pad to two digits @@ -495,8 +505,8 @@ TimePattern TimePattern::parse(const std::string& s) } std::size_t start = s.find('T') + 1; std::size_t randomStart = s.find('A'); - clock::duration expires = parseDuration(s.substr(start, randomStart - start)); - clock::duration variance = std::chrono::seconds(0); + system_clock::duration expires = parseDuration(s.substr(start, randomStart - start)); + system_clock::duration variance = std::chrono::seconds(0); if (randomStart != std::string::npos) { variance = parseDuration(s.substr(randomStart + 1)); @@ -507,8 +517,8 @@ TimePattern TimePattern::parse(const std::string& s) { // Recurring time Weekdays days = Weekdays::parse(s.substr(1, 3)); - clock::duration time = parseDuration(s.substr(6)); - clock::duration variation {0}; + system_clock::duration time = parseDuration(s.substr(6)); + system_clock::duration variation {0}; if (s.size() > 14) { variation = parseDuration(s.substr(15)); @@ -526,8 +536,8 @@ TimePattern TimePattern::parse(const std::string& s) // Time interval std::size_t start = s.find('T') + 1; std::size_t end = s.find('/', start); - clock::duration startTime = parseDuration(s.substr(start, end - start)); - clock::duration endTime = parseDuration(s.substr(end + 2)); + system_clock::duration startTime = parseDuration(s.substr(start, end - start)); + system_clock::duration endTime = parseDuration(s.substr(end + 2)); return TimePattern(TimeInterval(startTime, endTime, days)); } throw HueException(CURRENT_FILE_INFO, "Unable to parse time string: " + s); diff --git a/test/test_TimePattern.cpp b/test/test_TimePattern.cpp index 9b50dcf..d60e113 100644 --- a/test/test_TimePattern.cpp +++ b/test/test_TimePattern.cpp @@ -299,7 +299,7 @@ TEST(Timer, toString) EXPECT_EQ("PT00:01:20A01:00:00", timer.toString()); } { - const Timer timer(1min + 20s, 0); + const Timer timer(1min + 20s, Timer::infiniteExecutions); EXPECT_EQ("R/PT00:01:20", timer.toString()); } { @@ -315,7 +315,7 @@ TEST(Timer, toString) EXPECT_EQ("R05/PT00:01:20A01:00:00", timer.toString()); } { - const Timer timer(1min + 20s, 0, 1h); + const Timer timer(1min + 20s, Timer::infiniteExecutions, 1h); EXPECT_EQ("R/PT00:01:20A01:00:00", timer.toString()); } } @@ -449,7 +449,7 @@ TEST(TimePattern, Timer) EXPECT_EQ(expected.getNumberOfExecutions(), pattern.asTimer().getNumberOfExecutions()); } { - const Timer expected(1h + 30min + 20s, 0); + const Timer expected(1h + 30min + 20s, Timer::infiniteExecutions); const TimePattern pattern = TimePattern::parse("R/PT01:30:20"); ASSERT_EQ(TimePattern::Type::timer, pattern.getType()); EXPECT_EQ(expected.getExpiryTime(), pattern.asTimer().getExpiryTime()); @@ -457,7 +457,7 @@ TEST(TimePattern, Timer) EXPECT_EQ(expected.getNumberOfExecutions(), pattern.asTimer().getNumberOfExecutions()); } { - const Timer expected(1h + 30min + 20s, 0, 20s); + const Timer expected(1h + 30min + 20s, Timer::infiniteExecutions, 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());