Added espchrono.h/cpp
This commit is contained in:
9
CMakeLists.txt
Normal file
9
CMakeLists.txt
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
set(headers
|
||||||
|
src/espchrono.h
|
||||||
|
)
|
||||||
|
|
||||||
|
set(sources
|
||||||
|
src/espchrono.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
idf_component_register(INCLUDE_DIRS src SRCS ${headers} ${sources} REQUIRES freertos esp_system cpputils date espcpputils)
|
250
src/espchrono.cpp
Normal file
250
src/espchrono.cpp
Normal file
@@ -0,0 +1,250 @@
|
|||||||
|
#include "espchrono.h"
|
||||||
|
|
||||||
|
using namespace std::chrono_literals;
|
||||||
|
using namespace date;
|
||||||
|
|
||||||
|
namespace espchrono {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
static const uint8_t _monthDays[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
|
||||||
|
|
||||||
|
bool summerTime(local_clock::time_point timeStamp)
|
||||||
|
{
|
||||||
|
LocalDateTime _tempDateTime = toDateTime(timeStamp);
|
||||||
|
|
||||||
|
if (_tempDateTime.date.month() < March || _tempDateTime.date.month() > October) return false; // keine Sommerzeit in Jan, Feb, Nov, Dez
|
||||||
|
if (_tempDateTime.date.month() > March && _tempDateTime.date.month() < October) return true; // Sommerzeit in Apr, Mai, Jun, Jul, Aug, Sep
|
||||||
|
return
|
||||||
|
(_tempDateTime.date.month() == March && (_tempDateTime.hour + 24 * unsigned(_tempDateTime.date.day())) >= (3 + 24 * (31 - (5 * int(_tempDateTime.date.year()) / 4 + 4) % 7))) ||
|
||||||
|
(_tempDateTime.date.month() == October && (_tempDateTime.hour + 24 * unsigned(_tempDateTime.date.day())) < (3 + 24 * (31 - (5 * int(_tempDateTime.date.year()) / 4 + 1) % 7)));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool daylightSavingTime(local_clock::time_point _timeStamp)
|
||||||
|
{
|
||||||
|
LocalDateTime _tempDateTime = toDateTime(_timeStamp);
|
||||||
|
|
||||||
|
// see http://stackoverflow.com/questions/5590429/calculating-daylight-saving-time-from-only-date
|
||||||
|
// since 2007 DST begins on second Sunday of March and ends on first Sunday of November.
|
||||||
|
// Time change occurs at 2AM locally
|
||||||
|
if (_tempDateTime.date.month() < March || _tempDateTime.date.month() > November) return false; //January, february, and december are out.
|
||||||
|
if (_tempDateTime.date.month() > March && _tempDateTime.date.month() < November) return true; //April to October are in
|
||||||
|
|
||||||
|
int previousSunday = unsigned(_tempDateTime.date.day()) - (_tempDateTime.dayOfWeek - 1); // dow Sunday input was 1,
|
||||||
|
// need it to be Sunday = 0. If 1st of month = Sunday, previousSunday=1-0=1
|
||||||
|
//int previousSunday = day - (dow-1);
|
||||||
|
// -------------------- March ---------------------------------------
|
||||||
|
//In march, we are DST if our previous Sunday was = to or after the 8th.
|
||||||
|
if (_tempDateTime.date.month() == March) { // in march, if previous Sunday is after the 8th, is DST
|
||||||
|
// unless Sunday and hour < 2am
|
||||||
|
if (previousSunday >= 8) { // Sunday = 1
|
||||||
|
// return true if day > 14 or (dow == 1 and hour >= 2)
|
||||||
|
return ((_tempDateTime.date.day() > 14_d) ||
|
||||||
|
((_tempDateTime.dayOfWeek == 1 && _tempDateTime.hour >= 2) || _tempDateTime.dayOfWeek > 1));
|
||||||
|
} // end if ( previousSunday >= 8 && _dateTime.dayofWeek > 0 )
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// previousSunday has to be < 8 to get here
|
||||||
|
//return (previousSunday < 8 && (_tempDateTime.dayofWeek - 1) = 0 && _tempDateTime.hour >= 2)
|
||||||
|
return false;
|
||||||
|
} // end else
|
||||||
|
} // end if (_tempDateTime.date.month == 3 )
|
||||||
|
|
||||||
|
// ------------------------------- November -------------------------------
|
||||||
|
|
||||||
|
// gets here only if month = November
|
||||||
|
//In november we must be before the first Sunday to be dst.
|
||||||
|
//That means the previous Sunday must be before the 2nd.
|
||||||
|
if (previousSunday < 1)
|
||||||
|
{
|
||||||
|
// is not true for Sunday after 2am or any day after 1st Sunday any time
|
||||||
|
return ((_tempDateTime.dayOfWeek == 1 && _tempDateTime.hour < 2) || (_tempDateTime.dayOfWeek > 1));
|
||||||
|
//return true;
|
||||||
|
} // end if (previousSunday < 1)
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// return false unless after first wk and dow = Sunday and hour < 2
|
||||||
|
return (_tempDateTime.date.day() < 8_d && _tempDateTime.dayOfWeek == 1 && _tempDateTime.hour < 2);
|
||||||
|
} // end else
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
local_clock::time_point utcToLocal(utc_clock::time_point utc, time_zone timezone)
|
||||||
|
{
|
||||||
|
local_clock::time_point local{utc.time_since_epoch() + timezone.offset, timezone, false};
|
||||||
|
|
||||||
|
switch (timezone.dayLightSavingMode)
|
||||||
|
{
|
||||||
|
case DayLightSavingMode::None: break;
|
||||||
|
case DayLightSavingMode::EuropeanSummerTime: if (summerTime(local)) local.dst = true; break;
|
||||||
|
case DayLightSavingMode::UsDaylightTime: if (daylightSavingTime(local)) local.dst = true; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (local.dst)
|
||||||
|
local += 1h;
|
||||||
|
|
||||||
|
return local;
|
||||||
|
}
|
||||||
|
|
||||||
|
utc_clock::time_point localToUtc(local_clock::time_point local)
|
||||||
|
{
|
||||||
|
utc_clock::time_point utc{local.time_since_epoch()};
|
||||||
|
|
||||||
|
if (local.dst)
|
||||||
|
utc -= 1h;
|
||||||
|
|
||||||
|
utc -= local.timezone.offset;
|
||||||
|
|
||||||
|
return utc;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
DateTime toDateTime(seconds ts)
|
||||||
|
{
|
||||||
|
auto _time = ts.count();
|
||||||
|
|
||||||
|
uint8_t second(_time % 60);
|
||||||
|
_time /= 60; // now it is minutes
|
||||||
|
uint8_t minute(_time % 60);
|
||||||
|
_time /= 60; // now it is hours
|
||||||
|
uint8_t hour(_time % 24);
|
||||||
|
_time /= 24; // now it is _days
|
||||||
|
auto dayOfWeek = DateTime::DayOfWeek(((_time + 4) % 7) + 1); // Sunday is day 1
|
||||||
|
|
||||||
|
date::year year{1970};
|
||||||
|
unsigned long _days = 0;
|
||||||
|
while ((unsigned)(_days += (year.is_leap() ? 366 : 365)) <= _time) {
|
||||||
|
year++;
|
||||||
|
}
|
||||||
|
|
||||||
|
_days -= year.is_leap() ? 366 : 365;
|
||||||
|
_time -= _days; // now it is days in this year, starting at 0
|
||||||
|
|
||||||
|
_days = 0;
|
||||||
|
uint8_t _monthLength = 0;
|
||||||
|
date::month month = January;
|
||||||
|
for (; month <= December; month++) {
|
||||||
|
if (month == February) { // february
|
||||||
|
if (year.is_leap()) {
|
||||||
|
_monthLength = 29;
|
||||||
|
} else {
|
||||||
|
_monthLength = 28;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_monthLength = _monthDays[unsigned(month)-1];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_time >= _monthLength) {
|
||||||
|
_time -= _monthLength;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
date::day day(_time + 1); // day of month
|
||||||
|
|
||||||
|
DateTime dateTime{year_month_day{year, month, day}};
|
||||||
|
dateTime.hour=hour;
|
||||||
|
dateTime.minute=minute;
|
||||||
|
dateTime.second=second;
|
||||||
|
dateTime.dayOfWeek=dayOfWeek;
|
||||||
|
return dateTime;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DateTime toDateTime(utc_clock::time_point ts)
|
||||||
|
{
|
||||||
|
return toDateTime(ts.time_since_epoch());
|
||||||
|
}
|
||||||
|
|
||||||
|
LocalDateTime toDateTime(local_clock::time_point ts)
|
||||||
|
{
|
||||||
|
LocalDateTime dateTime = LocalDateTime{toDateTime(ts.time_since_epoch())};
|
||||||
|
dateTime.dst = ts.dst;
|
||||||
|
dateTime.timezone = ts.timezone;
|
||||||
|
return dateTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<DateTime> parseDateTime(std::string_view str)
|
||||||
|
{
|
||||||
|
// both valid:
|
||||||
|
// 2020-11-10T21:31
|
||||||
|
// 2020-11-10T21:31:10
|
||||||
|
|
||||||
|
int year;
|
||||||
|
unsigned month;
|
||||||
|
unsigned day;
|
||||||
|
uint8_t hour;
|
||||||
|
uint8_t minute;
|
||||||
|
uint8_t second{};
|
||||||
|
|
||||||
|
constexpr auto dateTimeFormat = "%4d-%2u-%2uT%2hhu:%2hhu:%2hhu";
|
||||||
|
if (const auto scanned = std::sscanf(str.data(), dateTimeFormat, &year, &month, &day, &hour, &minute, &second); scanned < 5)
|
||||||
|
return std::nullopt;
|
||||||
|
|
||||||
|
return DateTime{
|
||||||
|
.date=date::year_month_day{date::year{year}, date::month{month}, date::day{day}},
|
||||||
|
.hour=hour,
|
||||||
|
.minute=minute,
|
||||||
|
.second=second
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<seconds> parseDaypoint(std::string_view str)
|
||||||
|
{
|
||||||
|
int8_t hour, minute, second{};
|
||||||
|
|
||||||
|
constexpr auto daypointFormat = "%2hhd:%2hhd:%2hhd";
|
||||||
|
if (const auto scanned = std::sscanf(str.data(), daypointFormat, &hour, &minute, &second); scanned < 2)
|
||||||
|
return std::nullopt;
|
||||||
|
|
||||||
|
if (hour < 0 || hour > 23 || minute < 0 || minute > 59 || second < 0 || second > 59)
|
||||||
|
return std::nullopt;
|
||||||
|
|
||||||
|
return hours{hour} + minutes{minute} + seconds{second};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string toString(const DateTime &dateTime)
|
||||||
|
{
|
||||||
|
char buf[27];
|
||||||
|
|
||||||
|
std::sprintf(buf, "%04i-%02u-%02uT%02hhu:%02hhu:%02hhu",
|
||||||
|
int{dateTime.date.year()}, unsigned{dateTime.date.month()}, unsigned{dateTime.date.day()},
|
||||||
|
dateTime.hour, dateTime.minute, dateTime.second);
|
||||||
|
|
||||||
|
return std::string{buf};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string toString(const LocalDateTime &dateTime)
|
||||||
|
{
|
||||||
|
char buf[34];
|
||||||
|
|
||||||
|
date::hh_mm_ss helper{dateTime.timezone.offset + hours{dateTime.dst ? 1 : 0}};
|
||||||
|
|
||||||
|
std::sprintf(buf, "%04i-%02u-%02uT%02hhu:%02hhu:%02hhu %s%02hhu:%02hhu",
|
||||||
|
int{dateTime.date.year()}, unsigned{dateTime.date.month()}, unsigned{dateTime.date.day()},
|
||||||
|
dateTime.hour, dateTime.minute, dateTime.second,
|
||||||
|
helper.is_negative() ? "-" : "+", uint8_t(helper.hours().count()), uint8_t(helper.minutes().count()));
|
||||||
|
|
||||||
|
return std::string{buf};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string toDaypointString(seconds seconds)
|
||||||
|
{
|
||||||
|
date::hh_mm_ss helper(seconds);
|
||||||
|
char buf[10];
|
||||||
|
std::sprintf(buf, "%s%02hhd:%02hhd:%02hhd", helper.is_negative() ? "-" : "", int8_t(helper.hours().count()), int8_t(helper.minutes().count()), int8_t(helper.seconds().count()));
|
||||||
|
return std::string{buf};
|
||||||
|
}
|
||||||
|
|
||||||
|
milliseconds ago(millis_clock::time_point a)
|
||||||
|
{
|
||||||
|
return millis_clock::now() - a;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string toString(milliseconds val) { return std::to_string(val.count()) + "ms"; }
|
||||||
|
std::string toString(seconds val) { return std::to_string(val.count()) + "s"; }
|
||||||
|
std::string toString(minutes val) { return std::to_string(val.count()) + "min"; }
|
||||||
|
std::string toString(hours val) { return std::to_string(val.count()) + "h"; }
|
||||||
|
|
||||||
|
IMPLEMENT_TYPESAFE_ENUM(DayLightSavingMode, : uint8_t, DayLightSavingModeValues)
|
||||||
|
|
||||||
|
} // namespace espchrono
|
186
src/espchrono.h
Normal file
186
src/espchrono.h
Normal file
@@ -0,0 +1,186 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// system includes
|
||||||
|
#include <chrono>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <ratio>
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
|
// 3rdparty lib includes
|
||||||
|
#include <date/date.h>
|
||||||
|
|
||||||
|
// local includes
|
||||||
|
#include "cpptypesafeenum.h"
|
||||||
|
|
||||||
|
namespace espchrono {
|
||||||
|
|
||||||
|
using milliseconds = std::chrono::duration<long, std::milli>;
|
||||||
|
using seconds = std::chrono::duration<long>;
|
||||||
|
using minutes = std::chrono::duration<long, std::ratio<60>>;
|
||||||
|
using hours = std::chrono::duration<long, std::ratio<3600>>;
|
||||||
|
|
||||||
|
struct utc_clock
|
||||||
|
{
|
||||||
|
typedef seconds duration;
|
||||||
|
typedef duration::rep rep;
|
||||||
|
typedef duration::period period;
|
||||||
|
typedef std::chrono::time_point<utc_clock, duration> time_point;
|
||||||
|
|
||||||
|
//static_assert(utc_clock::duration::min() < utc_clock::duration::zero(),
|
||||||
|
// "a clock's minimum duration cannot be less than its epoch");
|
||||||
|
|
||||||
|
static constexpr bool is_steady = false;
|
||||||
|
|
||||||
|
static time_point now() noexcept;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define DayLightSavingModeValues(x) \
|
||||||
|
x(None) \
|
||||||
|
x(EuropeanSummerTime) \
|
||||||
|
x(UsDaylightTime)
|
||||||
|
DECLARE_TYPESAFE_ENUM(DayLightSavingMode, : uint8_t, DayLightSavingModeValues)
|
||||||
|
|
||||||
|
struct time_zone
|
||||||
|
{
|
||||||
|
minutes offset{};
|
||||||
|
DayLightSavingMode dayLightSavingMode{};
|
||||||
|
|
||||||
|
bool operator== (const time_zone &other) const
|
||||||
|
{
|
||||||
|
return offset == other.offset &&
|
||||||
|
dayLightSavingMode == other.dayLightSavingMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator!= (const time_zone &other) const
|
||||||
|
{
|
||||||
|
return offset != other.offset ||
|
||||||
|
dayLightSavingMode != other.dayLightSavingMode;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename _Clock, typename _Dur>
|
||||||
|
struct local_time_point : std::chrono::time_point<_Clock, _Dur>
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
using base_class = std::chrono::time_point<_Clock, _Dur>;
|
||||||
|
|
||||||
|
public:
|
||||||
|
using base_class::time_point;
|
||||||
|
|
||||||
|
constexpr local_time_point(const local_time_point &other) = default;
|
||||||
|
|
||||||
|
constexpr explicit local_time_point(_Dur duration, const time_zone &_timezone, bool _dst) :
|
||||||
|
base_class{duration}, timezone{_timezone}, dst{_dst}
|
||||||
|
{}
|
||||||
|
|
||||||
|
time_zone timezone{};
|
||||||
|
bool dst{};
|
||||||
|
|
||||||
|
bool operator== (const base_class &other) const = delete;
|
||||||
|
|
||||||
|
bool operator== (const local_time_point &other) const
|
||||||
|
{
|
||||||
|
return static_cast<const base_class &>(*this) == static_cast<const base_class &>(other) &&
|
||||||
|
timezone == other.timezone &&
|
||||||
|
dst == other.dst;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator!= (const base_class &other) const = delete;
|
||||||
|
|
||||||
|
bool operator!= (const local_time_point &other) const
|
||||||
|
{
|
||||||
|
return static_cast<const base_class &>(*this) != static_cast<const base_class &>(other) ||
|
||||||
|
timezone != other.timezone ||
|
||||||
|
dst != other.dst;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct local_clock
|
||||||
|
{
|
||||||
|
typedef seconds duration;
|
||||||
|
typedef duration::rep rep;
|
||||||
|
typedef duration::period period;
|
||||||
|
typedef local_time_point<local_clock, duration> time_point;
|
||||||
|
|
||||||
|
//static_assert(local_clock::duration::min() < local_clock::duration::zero(),
|
||||||
|
// "a clock's minimum duration cannot be less than its epoch");
|
||||||
|
|
||||||
|
static constexpr bool is_steady = false;
|
||||||
|
|
||||||
|
static time_point now() noexcept;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct millis_clock
|
||||||
|
{
|
||||||
|
typedef milliseconds duration;
|
||||||
|
typedef duration::rep rep;
|
||||||
|
typedef duration::period period;
|
||||||
|
typedef std::chrono::time_point<millis_clock, duration> time_point;
|
||||||
|
|
||||||
|
static constexpr bool is_steady = true;
|
||||||
|
|
||||||
|
static time_point now() noexcept;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DateTime
|
||||||
|
{
|
||||||
|
date::year_month_day date;
|
||||||
|
|
||||||
|
uint8_t hour{};
|
||||||
|
uint8_t minute{};
|
||||||
|
uint8_t second{};
|
||||||
|
|
||||||
|
enum DayOfWeek { Sunday = 1, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday };
|
||||||
|
DayOfWeek dayOfWeek{};
|
||||||
|
|
||||||
|
bool operator==(const DateTime &other) const
|
||||||
|
{
|
||||||
|
return date == other.date &&
|
||||||
|
hour == other.hour &&
|
||||||
|
minute == other.minute &&
|
||||||
|
second == other.second &&
|
||||||
|
dayOfWeek == other.dayOfWeek;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct LocalDateTime : public DateTime
|
||||||
|
{
|
||||||
|
time_zone timezone{};
|
||||||
|
bool dst{};
|
||||||
|
|
||||||
|
bool operator==(const LocalDateTime &other) const
|
||||||
|
{
|
||||||
|
return DateTime::operator==(other) &&
|
||||||
|
timezone == other.timezone &&
|
||||||
|
dst == other.dst;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
local_clock::time_point utcToLocal(utc_clock::time_point timeStamp, time_zone timezone);
|
||||||
|
utc_clock::time_point localToUtc(local_clock::time_point local);
|
||||||
|
|
||||||
|
DateTime toDateTime(utc_clock::time_point ts);
|
||||||
|
LocalDateTime toDateTime(local_clock::time_point ts);
|
||||||
|
|
||||||
|
//! Returns null if string cannot be parsed
|
||||||
|
std::optional<DateTime> parseDateTime(std::string_view str);
|
||||||
|
|
||||||
|
//! Returns null if string cannot be parsed
|
||||||
|
std::optional<seconds> parseDaypoint(std::string_view str);
|
||||||
|
|
||||||
|
std::string toString(const DateTime &dateTime);
|
||||||
|
|
||||||
|
std::string toString(const LocalDateTime &dateTime);
|
||||||
|
|
||||||
|
std::string toDaypointString(seconds seconds);
|
||||||
|
|
||||||
|
milliseconds ago(millis_clock::time_point a);
|
||||||
|
|
||||||
|
std::string toString(milliseconds val);
|
||||||
|
std::string toString(seconds val);
|
||||||
|
std::string toString(minutes val);
|
||||||
|
std::string toString(hours val);
|
||||||
|
} // namespace espchrono
|
Reference in New Issue
Block a user