-
Notifications
You must be signed in to change notification settings - Fork 146
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[math] Add :math:calendar module with DateTime class
- Loading branch information
Showing
5 changed files
with
335 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,235 @@ | ||
/* | ||
* Copyright (c) 2024, Niklas Hauser | ||
* | ||
* This file is part of the modm project. | ||
* | ||
* This Source Code Form is subject to the terms of the Mozilla Public | ||
* License, v. 2.0. If a copy of the MPL was not distributed with this | ||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. | ||
*/ | ||
// ---------------------------------------------------------------------------- | ||
|
||
#pragma once | ||
|
||
#include <chrono> | ||
#include <ctime> | ||
#include <charconv> | ||
#include <string_view> | ||
#include <cstring> | ||
|
||
namespace modm | ||
{ | ||
|
||
/// Efficient representation of a date and time | ||
/// @ingroup modm_math_calendar | ||
class DateTime | ||
{ | ||
public: | ||
using duration = std::chrono::milliseconds; | ||
|
||
constexpr DateTime() = default; | ||
|
||
constexpr explicit | ||
DateTime(uint16_t year, uint8_t month, uint8_t day, | ||
uint8_t hour = 0, uint8_t minute = 0, uint8_t second = 0, | ||
uint16_t millisecond = 0, uint8_t weekday = 0) | ||
: data(year - epoch, month, day, hour, minute, second, millisecond), _weekday(weekday) | ||
{} | ||
|
||
constexpr std::chrono::year | ||
year() const | ||
{ return std::chrono::year{epoch + data.year}; } | ||
|
||
constexpr std::chrono::month | ||
month() const | ||
{ return std::chrono::month{data.month}; } | ||
|
||
constexpr std::chrono::day | ||
day() const | ||
{ return std::chrono::day{data.day}; } | ||
|
||
constexpr std::chrono::year_month_day | ||
year_month_day() const | ||
{ return std::chrono::year_month_day{year(), month(), day()}; } | ||
|
||
constexpr std::chrono::weekday | ||
weekday() const | ||
{ return std::chrono::weekday{_weekday}; } | ||
|
||
constexpr std::chrono::days | ||
day_of_year() const | ||
{ | ||
uint16_t yday = m2d[data.month] + data.day - 1u; | ||
if ((data.year & 0b11) == 0b10 and data.month > 2u) yday++; | ||
return std::chrono::days{yday}; | ||
} | ||
|
||
|
||
constexpr std::chrono::hours | ||
hour() const | ||
{ return std::chrono::hours{data.hour}; } | ||
|
||
constexpr std::chrono::minutes | ||
minute() const | ||
{ return std::chrono::minutes{data.minute}; } | ||
|
||
constexpr std::chrono::seconds | ||
second() const | ||
{ return std::chrono::seconds{data.second}; } | ||
|
||
constexpr std::chrono::milliseconds | ||
millisecond() const | ||
{ return std::chrono::milliseconds{data.millisecond}; } | ||
|
||
constexpr std::chrono::hh_mm_ss<duration> | ||
hh_mm_ss() const | ||
{ return std::chrono::hh_mm_ss{time_since_epoch()}; } | ||
|
||
|
||
constexpr std::tm | ||
tm() const | ||
{ | ||
std::tm tm{}; | ||
|
||
tm.tm_sec = data.second; | ||
tm.tm_min = data.minute; | ||
tm.tm_hour = data.hour; | ||
|
||
tm.tm_mday = data.day; | ||
tm.tm_mon = data.month; | ||
tm.tm_year = data.year + epoch - 1900u; | ||
|
||
tm.tm_wday = weekday().c_encoding(); | ||
|
||
tm.tm_yday = day_of_year().count(); | ||
|
||
return tm; | ||
} | ||
|
||
constexpr std::time_t | ||
time_t() const | ||
{ | ||
const uint8_t leap_days_since_epoch = (data.year < 2u) ? 0u : (data.year - 2u) / 4u; | ||
return (data.year * seconds_per_year + | ||
(leap_days_since_epoch + day_of_year().count()) * seconds_per_day + | ||
(data.hour * 60l + data.minute) * 60l + data.second); | ||
} | ||
|
||
constexpr duration | ||
time_since_epoch() const | ||
{ | ||
return duration{time_t() * 1000ll + data.millisecond}; | ||
} | ||
|
||
|
||
constexpr auto operator<=>(const DateTime& other) const | ||
{ return data.value <=> other.data.value; } | ||
|
||
constexpr auto operator==(const DateTime& other) const | ||
{ return data.value == other.data.value; } | ||
|
||
private: | ||
union Data | ||
{ | ||
constexpr Data() = default; | ||
constexpr explicit | ||
Data(uint8_t year, uint8_t month, uint8_t day, | ||
uint8_t hour, uint8_t minute, uint8_t second, uint16_t millisecond) | ||
: millisecond(millisecond), second(second), minute(minute), hour(hour), | ||
day(day), month(month), year(year) {} | ||
struct | ||
{ | ||
uint16_t millisecond; | ||
uint8_t second; | ||
uint8_t minute; | ||
uint8_t hour; | ||
|
||
uint8_t day; | ||
uint8_t month; | ||
uint8_t year; | ||
} modm_packed; | ||
uint64_t value; | ||
}; | ||
|
||
Data data{}; | ||
uint8_t _weekday{}; | ||
|
||
static constexpr uint16_t epoch{1970}; | ||
static constexpr uint32_t seconds_per_day{24*60*60}; | ||
static constexpr uint64_t seconds_per_year{365*seconds_per_day}; | ||
// accumulated (non-leap) days per month, 1-indexed! | ||
static constexpr uint16_t m2d[] = {0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334}; | ||
|
||
public: | ||
static constexpr DateTime | ||
from_tm(const std::tm& tm) | ||
{ | ||
return DateTime{ | ||
uint16_t(tm.tm_year + 1900 - epoch), uint8_t(tm.tm_mon), uint8_t(tm.tm_mday), | ||
uint8_t(tm.tm_hour), uint8_t(tm.tm_min), uint8_t(tm.tm_sec), 0u, uint8_t(tm.tm_wday)}; | ||
} | ||
|
||
// static constexpr DateTime | ||
// from_time_t(std::time_t time) | ||
// { | ||
// return DateTime{}; | ||
// } | ||
|
||
static consteval DateTime | ||
fromBuildTime() | ||
{ | ||
// Example: "Mon Dec 23 17:45:35 2024" | ||
static constexpr std::string_view timestamp{__TIMESTAMP__}; | ||
constexpr auto to_uint = [&](uint8_t offset, uint8_t length) -> uint16_t | ||
{ | ||
const auto str = timestamp.substr(offset, length); | ||
int integer; | ||
(void) std::from_chars(str.begin(), str.end(), integer); | ||
return uint16_t(integer); | ||
}; | ||
// All easy to parse integers | ||
const uint16_t cyear{to_uint(20, 4)}; | ||
const uint8_t cday{to_uint(8, 2)}; | ||
const uint8_t chour{to_uint(11, 2)}; | ||
const uint8_t cminute{to_uint(14, 2)}; | ||
const uint8_t csecond{to_uint(17, 2)}; | ||
|
||
// Annoying to parse strings | ||
static constexpr std::string_view months[] | ||
{"", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; | ||
uint8_t cmonth{1}; | ||
for (; months[cmonth] != timestamp.substr(4, 3) and cmonth <= 12; ++cmonth) ; | ||
|
||
static constexpr std::string_view weekdays[] | ||
{"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; | ||
uint8_t cweekday{}; | ||
for (; weekdays[cweekday] != timestamp.substr(0, 3) and cweekday < 7; ++cweekday) ; | ||
|
||
return DateTime{cyear, cmonth, cday, chour, cminute, csecond, 0, cweekday}; | ||
} | ||
}; | ||
|
||
} // namespace modm | ||
|
||
#if MODM_HAS_IOSTREAM | ||
#include <inttypes.h> | ||
#include <modm/io/iostream.hpp> | ||
|
||
namespace modm | ||
{ | ||
|
||
/// @ingroup modm_math_calendar | ||
inline modm::IOStream& | ||
operator << (modm::IOStream& s, const DateTime& dt) | ||
{ | ||
// ISO encoding: 2024-12-22 18:39:21.342 | ||
s.printf("%04" PRIu16 "-%02" PRIu8 "-%02" PRIu8 " %02" PRIu8 ":%02" PRIu8 ":%02" PRIu8 ".%03" PRIu16, | ||
uint16_t(int(dt.year())), uint8_t(unsigned(dt.month())), uint8_t(unsigned(dt.day())), | ||
uint8_t(dt.hour().count()), uint8_t(dt.minute().count()), uint8_t(dt.second().count()), | ||
uint16_t(dt.millisecond().count())); | ||
return s; | ||
} | ||
|
||
} // modm namespace | ||
|
||
#endif |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
#!/usr/bin/env python3 | ||
# -*- coding: utf-8 -*- | ||
# | ||
# Copyright (c) 2024, Niklas Hauser | ||
# | ||
# This file is part of the modm project. | ||
# | ||
# This Source Code Form is subject to the terms of the Mozilla Public | ||
# License, v. 2.0. If a copy of the MPL was not distributed with this | ||
# file, You can obtain one at http://mozilla.org/MPL/2.0/. | ||
# ----------------------------------------------------------------------------- | ||
|
||
def init(module): | ||
module.name = ":math:calendar" | ||
module.description = "Calendar Operations" | ||
|
||
def prepare(module, options): | ||
module.depends(":stdc++") | ||
# libstdc++ does not provide support for <charconv> | ||
return options[":target"].identifier.platform != "avr" | ||
|
||
def build(env): | ||
env.outbasepath = "modm/src/modm/math/calendar" | ||
env.copy(".") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
/* | ||
* Copyright (c) 2009-2011, Fabian Greif | ||
* Copyright (c) 2010, Martin Rosekeit | ||
* Copyright (c) 2012, Niklas Hauser | ||
* Copyright (c) 2012, Sascha Schade | ||
* | ||
* This file is part of the modm project. | ||
* | ||
* This Source Code Form is subject to the terms of the Mozilla Public | ||
* License, v. 2.0. If a copy of the MPL was not distributed with this | ||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. | ||
*/ | ||
// ---------------------------------------------------------------------------- | ||
|
||
#include "datetime_test.hpp" | ||
#include <modm/math/calendar/date_time.hpp> | ||
|
||
void | ||
DateTimeTest::testConversion() | ||
{ | ||
const auto dt1 = modm::DateTime(1970, 1, 1, 0, 0, 0); | ||
TEST_ASSERT_EQUALS(dt1.day_of_year().count(), 0l); | ||
TEST_ASSERT_EQUALS(dt1.time_t(), 0l); | ||
|
||
const auto dt2 = modm::DateTime(1970, 1, 1, 0, 0, 1); | ||
TEST_ASSERT_EQUALS(dt2.day_of_year().count(), 0l); | ||
TEST_ASSERT_EQUALS(dt2.time_t(), 1l); | ||
TEST_ASSERT_TRUE(dt1 < dt2); | ||
TEST_ASSERT_TRUE(dt1 <= dt2); | ||
TEST_ASSERT_FALSE(dt1 == dt2); | ||
TEST_ASSERT_FALSE(dt1 >= dt2); | ||
TEST_ASSERT_FALSE(dt1 > dt2); | ||
|
||
// first leap year since epoch | ||
const auto dt3 = modm::DateTime(1972, 3, 1, 0, 0, 0); | ||
TEST_ASSERT_EQUALS(dt3.day_of_year().count(), 31l+29l); | ||
TEST_ASSERT_EQUALS(dt3.time_t(), 24*60*60*(365*2+31+29)); // 1 day too long? | ||
TEST_ASSERT_TRUE(dt1 < dt3); | ||
TEST_ASSERT_TRUE(dt2 < dt3); | ||
|
||
const auto dt4 = modm::DateTime(2024, 12, 24, 12, 24, 12); | ||
TEST_ASSERT_EQUALS(dt4.day_of_year().count(), 358); | ||
TEST_ASSERT_EQUALS(dt4.time_t(), 1735043052); | ||
TEST_ASSERT_TRUE(dt1 < dt4); | ||
TEST_ASSERT_TRUE(dt2 < dt4); | ||
TEST_ASSERT_TRUE(dt3 < dt4); | ||
TEST_ASSERT_EQUALS(dt4, dt4); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
/* | ||
* Copyright (c) 2024, Niklas Hauser | ||
* | ||
* This file is part of the modm project. | ||
* | ||
* This Source Code Form is subject to the terms of the Mozilla Public | ||
* License, v. 2.0. If a copy of the MPL was not distributed with this | ||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. | ||
*/ | ||
// ---------------------------------------------------------------------------- | ||
|
||
#include <unittest/testsuite.hpp> | ||
|
||
/// @ingroup modm_test_test_math | ||
class DateTimeTest : public unittest::TestSuite | ||
{ | ||
public: | ||
void | ||
testConversion(); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters