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