feat: initialize Nocal terminal calendar

This commit is contained in:
2026-07-17 21:24:06 +01:00
commit 22c6399056
37 changed files with 6146 additions and 0 deletions

134
tests/domain_tests.cpp Normal file
View File

@@ -0,0 +1,134 @@
#include "nocal/domain/domain.hpp"
#include <chrono>
#include <cstdlib>
#include <iostream>
#include <stdexcept>
#include <string_view>
#include <utility>
#include <vector>
namespace {
int failures = 0;
void check(bool condition, std::string_view message)
{
if (!condition) {
++failures;
std::cerr << "FAIL: " << message << '\n';
}
}
template <class Function>
void check_throws(Function&& function, std::string_view message)
{
try {
function();
check(false, message);
} catch (const std::exception&) {
}
}
void test_dates()
{
using namespace std::chrono;
const nocal::Date leap{2024y / February / 29d};
check(nocal::valid_date(leap), "leap day is valid");
check(nocal::format_date(leap) == "2024-02-29", "date formats as ISO 8601");
check(nocal::parse_date("2024-02-29") == leap, "date parses from ISO 8601");
check(nocal::from_sys_days(nocal::to_sys_days(leap) + days{1}) ==
nocal::Date{2024y / March / 1d},
"civil date arithmetic crosses a leap month");
check(nocal::monday_first_weekday(nocal::Date{2026y / July / 6d}) == 0,
"Monday has index zero");
check(nocal::monday_first_weekday(nocal::Date{2026y / July / 12d}) == 6,
"Sunday has index six");
check_throws([] { (void)nocal::parse_date("2023-02-29"); },
"invalid date is rejected");
}
void test_month_layout()
{
using namespace std::chrono;
const auto layout = nocal::make_month_layout(2026y / July);
check(layout.cells.size() == 42, "month layout always has 42 cells");
check(layout.first_visible_date() == nocal::Date{2026y / June / 29d},
"July 2026 begins after Monday-leading spill cells");
check(layout.last_visible_date() == nocal::Date{2026y / August / 9d},
"six-week grid has the expected final date");
check(layout.at(0, 0).column == 0 && layout.at(5, 6).row == 5,
"cell coordinates match their positions");
check(!layout.at(0, 0).in_month && layout.at(0, 2).in_month,
"spill cells are distinguished from current-month cells");
const auto* july_17 = layout.find(nocal::Date{2026y / July / 17d});
check(july_17 != nullptr && july_17->row == 2 && july_17->column == 4,
"date lookup finds the correct Friday cell");
check_throws([&] { (void)layout.at(6, 0); }, "out-of-grid access is rejected");
}
nocal::Event event(std::string uid, nocal::Date start_date, unsigned start_hour,
nocal::Date end_date, unsigned end_hour, bool all_day = false)
{
nocal::Event result;
result.uid = std::move(uid);
result.title = result.uid;
result.start = nocal::make_local_time(start_date, start_hour);
result.end = nocal::make_local_time(end_date, end_hour);
result.all_day = all_day;
return result;
}
void test_event_queries()
{
using namespace std::chrono;
const nocal::Date previous{2026y / July / 16d};
const nocal::Date day{2026y / July / 17d};
const nocal::Date next{2026y / July / 18d};
std::vector<nocal::Event> events;
events.push_back(event("late", day, 15, day, 16));
events.push_back(event("all-day", day, 0, next, 0, true));
events.push_back(event("early", day, 9, day, 10));
events.push_back(event("carry", previous, 23, day, 1));
events.push_back(event("ends-at-boundary", previous, 22, day, 0));
events.push_back(event("next-day", next, 0, next, 1));
const nocal::Date august_first{2026y / August / 1d};
events.push_back(event("next-month", august_first, 0, august_first, 1));
nocal::Event instant = event("instant", day, 12, day, 12);
events.push_back(instant);
const auto on_day = nocal::events_on_day(events, day);
check(on_day.size() == 5, "day query uses overlap and half-open boundaries");
check(on_day[0].get().uid == "all-day", "all-day events sort before timed events");
check(on_day[1].get().uid == "carry", "overlapping prior-day event is included");
check(on_day[2].get().uid == "early" && on_day[3].get().uid == "instant" &&
on_day[4].get().uid == "late",
"timed events sort by start time");
check(nocal::event_occurs_on(instant, day),
"zero-duration event occurs on the day containing its instant");
const auto in_july = nocal::events_in_month(events, 2026y / July);
check(in_july.size() == 7, "month query excludes an event beginning August 1");
nocal::Event invalid = event("invalid", day, 12, day, 11);
check(!nocal::event_overlaps(invalid, nocal::local_day_start(day),
nocal::local_day_end(day)),
"invalid backwards event never overlaps");
}
} // namespace
int main()
{
test_dates();
test_month_layout();
test_event_queries();
if (failures != 0) {
std::cerr << failures << " domain test(s) failed\n";
return EXIT_FAILURE;
}
std::cout << "domain tests passed\n";
return EXIT_SUCCESS;
}