/**
\file TimePattern.cpp
Copyright Notice\n
Copyright (C) 2020 Jan Rogall - developer\n
This file is part of hueplusplus.
hueplusplus is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
hueplusplus is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with hueplusplus. If not, see .
**/
#include
#include
#include
namespace hueplusplus
{
namespace time
{
using clock = std::chrono::system_clock;
std::string timepointToTimestamp(clock::time_point time)
{
using namespace std::chrono;
std::time_t ctime = clock::to_time_t(time);
std::tm localtime = *std::localtime(&ctime);
char buf[32];
std::size_t result = std::strftime(buf, sizeof(buf), "%FT%T", &localtime);
if (result == 0)
{
throw HueException(CURRENT_FILE_INFO, "strftime failed");
}
return std::string(buf);
}
clock::time_point parseTimestamp(const std::string& timestamp)
{
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));
tm.tm_sec = std::stoi(timestamp.substr(17, 2));
// Auto detect daylight savings time
tm.tm_isdst = -1;
std::time_t ctime = std::mktime(&tm);
return clock::from_time_t(ctime);
}
std::string durationTo_hh_mm_ss(clock::duration duration)
{
using namespace std::chrono;
if (duration > hours(24))
{
throw HueException(CURRENT_FILE_INFO, "Duration parameter longer than 1 day");
}
int numH = static_cast(duration_cast(duration).count());
duration -= hours(numH);
int numM = static_cast(duration_cast(duration).count());
duration -= minutes(numM);
int numS = static_cast(duration_cast(duration).count());
char result[9];
std::sprintf(result, "%02d:%02d:%02d", numH, numM, numS);
return std::string(result);
}
clock::duration parseDuration(const std::string& s)
{
using namespace std::chrono;
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() const
{
return base;
}
clock::duration AbsoluteTime::getRandomVariation() const
{
return variation;
}
std::string AbsoluteTime::toString() const
{
std::string result = timepointToTimestamp(base);
if (variation.count() != 0)
{
result.push_back('A');
result.append(durationTo_hh_mm_ss(variation));
}
return result;
}
bool Weekdays::isNone() const
{
return bitmask == 0;
}
bool Weekdays::isAll() const
{
// Check all 7 bits are set
return bitmask == (1 << 7) - 1;
}
bool Weekdays::isMonday() const
{
return (bitmask & 1) != 0;
}
bool Weekdays::isTuesday() const
{
return (bitmask & 2) != 0;
}
bool Weekdays::isWednesday() const
{
return (bitmask & 4) != 0;
}
bool Weekdays::isThursday() const
{
return (bitmask & 8) != 0;
}
bool Weekdays::isFriday() const
{
return (bitmask & 16) != 0;
}
bool Weekdays::isSaturday() const
{
return (bitmask & 32) != 0;
}
bool Weekdays::isSunday() const
{
return (bitmask & 64) != 0;
}
std::string Weekdays::toString() const
{
std::string result = std::to_string(bitmask);
if (result.size() < 3)
{
result.insert(0, 3 - result.size(), '0');
}
return result;
}
Weekdays Weekdays::unionWith(Weekdays other) const
{
other.bitmask |= bitmask;
return other;
}
Weekdays Weekdays::none()
{
return Weekdays();
}
Weekdays Weekdays::all()
{
Weekdays result;
result.bitmask = (1 << 7) - 1;
return result;
}
Weekdays Weekdays::monday()
{
return Weekdays(0);
}
Weekdays Weekdays::tuesday()
{
return Weekdays(1);
}
Weekdays Weekdays::wednesday()
{
return Weekdays(2);
}
Weekdays Weekdays::thursday()
{
return Weekdays(3);
}
Weekdays Weekdays::friday()
{
return Weekdays(4);
}
Weekdays Weekdays::saturday()
{
return Weekdays(5);
}
Weekdays Weekdays::sunday()
{
return Weekdays(6);
}
Weekdays Weekdays::parse(const std::string& s)
{
Weekdays result;
result.bitmask = std::stoi(s);
return result;
}
RecurringTime::RecurringTime(clock::duration daytime, Weekdays days, clock::duration variation)
: time(daytime), days(days), variation(variation)
{}
clock::duration RecurringTime::getDaytime() const
{
return time;
}
clock::duration RecurringTime::getRandomVariation() const
{
return variation;
}
Weekdays RecurringTime::getWeekdays() const
{
return days;
}
std::string RecurringTime::toString() const
{
std::string result = "W";
result.append(days.toString());
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 result;
}
TimeInterval::TimeInterval(clock::duration start, clock::duration end, Weekdays days)
: start(start), end(end), days(days)
{}
clock::duration TimeInterval::getStartTime() const
{
return start;
}
clock::duration TimeInterval::getEndTime() const
{
return end;
}
Weekdays TimeInterval::getWeekdays() const
{
return days;
}
std::string TimeInterval::toString() const
{
std::string result;
if (!days.isAll())
{
result.append("W");
result.append(days.toString());
result.append("/");
}
result.push_back('T');
result.append(durationTo_hh_mm_ss(start));
result.append("/T");
result.append(durationTo_hh_mm_ss(end));
return result;
}
Timer::Timer(clock::duration duration, clock::duration variation)
: expires(duration), numExecutions(1), variation(variation)
{}
Timer::Timer(clock::duration duration, int numExecutions, clock::duration variation)
: expires(duration), numExecutions(numExecutions), variation(variation)
{}
bool Timer::isRecurring() const
{
return numExecutions != 1;
}
int Timer::getNumberOfExecutions() const
{
return numExecutions;
}
clock::duration Timer::getExpiryTime() const
{
return expires;
}
clock::duration Timer::getRandomVariation() const
{
return variation;
}
std::string Timer::toString() const
{
std::string result;
if (numExecutions != 1)
{
result.push_back('R');
if (numExecutions != 0)
{
std::string s = std::to_string(numExecutions);
// Pad to two digits
if (s.size() < 2)
{
result.push_back('0');
}
result.append(s);
}
result.push_back('/');
}
result.append("PT");
result.append(durationTo_hh_mm_ss(expires));
if (variation.count() != 0)
{
result.push_back('A');
result.append(durationTo_hh_mm_ss(variation));
}
return result;
}
TimePattern::TimePattern() : type(Type::undefined), undefined(nullptr) {}
TimePattern::~TimePattern()
{
destroy();
}
TimePattern::TimePattern(const AbsoluteTime& absolute) : type(Type::absolute), absolute(absolute) {}
TimePattern::TimePattern(const RecurringTime& recurring) : type(Type::recurring), recurring(recurring) {}
TimePattern::TimePattern(const TimeInterval& interval) : type(Type::interval), interval(interval) {}
TimePattern::TimePattern(const Timer& timer) : type(Type::timer), timer(timer) {}
TimePattern::TimePattern(const TimePattern& other) : type(Type::undefined), undefined(nullptr)
{
*this = other;
}
TimePattern& TimePattern::operator=(const TimePattern& other)
{
if (this == &other)
{
return *this;
}
destroy();
try
{
type = other.type;
switch (type)
{
case Type::undefined:
undefined = nullptr;
break;
case Type::absolute:
new (&absolute) AbsoluteTime(other.absolute);
break;
case Type::recurring:
new (&recurring) RecurringTime(other.recurring);
break;
case Type::interval:
new (&interval) TimeInterval(other.interval);
break;
case Type::timer:
new (&timer) Timer(other.timer);
break;
}
}
catch (...)
{
// Catch any throws from constructors to stay in valid state
type = Type::undefined;
undefined = nullptr;
throw;
}
return *this;
}
TimePattern::Type TimePattern::getType() const
{
return type;
}
AbsoluteTime TimePattern::asAbsolute() const
{
return absolute;
}
RecurringTime TimePattern::asRecurring() const
{
return recurring;
}
TimeInterval TimePattern::asInterval() const
{
return interval;
}
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")
{
return TimePattern();
}
else if (std::isdigit(s.front()))
{
// Absolute time
clock::time_point time = parseTimestamp(s);
clock::duration variation {0};
if (s.size() > 19 && s[19] == 'A')
{
// Random variation
variation = parseDuration(s.substr(20));
}
return TimePattern(AbsoluteTime(time, variation));
}
else if (s.front() == 'R' || s.front() == 'P')
{
// (Recurring) timer
int numRepetitions = 1;
if (s.front() == 'R')
{
if (s.at(1) == '/')
{
// Infinite
numRepetitions = 0;
}
else
{
numRepetitions = std::stoi(s.substr(1, 2));
}
}
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);
if (randomStart != std::string::npos)
{
variance = parseDuration(s.substr(randomStart+1));
}
return TimePattern(Timer(expires, numRepetitions, variance));
}
else if (s.front() == 'W' && std::count(s.begin(), s.end(), '/') == 1)
{
// Recurring time
Weekdays days = Weekdays::parse(s.substr(1, 3));
clock::duration time = parseDuration(s.substr(6));
clock::duration variation {0};
if (s.size() > 14)
{
variation = parseDuration(s.substr(15));
}
return TimePattern(RecurringTime(time, days, variation));
}
else if (s.front() == 'T' || s.front() == 'W')
{
Weekdays days = Weekdays::all();
if (s.front() == 'W')
{
// Time interval with weekdays
days = Weekdays::parse(s.substr(1, 3));
}
// 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));
return TimePattern(TimeInterval(startTime, endTime, days));
}
throw HueException(CURRENT_FILE_INFO, "Unable to parse time string: " + s);
}
void TimePattern::destroy()
{
switch (type)
{
case Type::absolute:
absolute.~AbsoluteTime();
break;
case Type::recurring:
recurring.~RecurringTime();
break;
case Type::interval:
interval.~TimeInterval();
break;
case Type::timer:
timer.~Timer();
break;
default:
// Do not throw exception, because it is called in destructor
// just ignore
break;
}
type = Type::undefined;
undefined = nullptr;
}
} // namespace time
} // namespace hueplusplus