Added espchrono.h/cpp

This commit is contained in:
2021-02-14 21:02:29 +01:00
parent 1277623bcb
commit c2638be69f
3 changed files with 445 additions and 0 deletions

9
CMakeLists.txt Normal file
View 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
View 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
View 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