/* * Time.cpp * Copyright (c) 2025 Daniel Hammer */ #include "Time.hpp" #include "Timer.hpp" #include #include using namespace Kt; static int64_t g_bootEpoch = 0; static int g_tzOffsetMinutes = 60; /* Default: UTC+1 (CET) until userspace overrides */ static bool IsLeapYear(int year) { return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0); } static int DaysInMonth(int month, int year) { static const int days[] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; if (month == 2 && IsLeapYear(year)) return 29; return days[month]; } static int64_t DateToEpoch(int year, int month, int day, int hour, int minute, int second) { int64_t days = 0; for (int y = 1970; y < year; y++) { days += IsLeapYear(y) ? 366 : 365; } for (int m = 1; m < month; m++) { days += DaysInMonth(m, year); } days += day - 1; return days * 86400 + hour * 3600 + minute * 60 + second; } static Timekeeping::DateTime EpochToDate(int64_t epoch) { Timekeeping::DateTime dt = {}; /* Before the first NTP sync on a board with no RTC the clock sits near the epoch, and a negative time zone pushes local time before it. */ int64_t rem = epoch < 0 ? 0 : epoch; int year = 1970; while (true) { int64_t daysInYear = IsLeapYear(year) ? 366 : 365; if (rem < daysInYear * 86400) break; rem -= daysInYear * 86400; year++; } dt.Year = (uint16_t)year; int dayOfYear = (int)(rem / 86400); rem -= (int64_t)dayOfYear * 86400; int month = 1; while (month <= 12) { int dim = DaysInMonth(month, year); if (dayOfYear < dim) break; dayOfYear -= dim; month++; } dt.Month = (uint8_t)month; dt.Day = (uint8_t)(dayOfYear + 1); dt.Hour = (uint8_t)(rem / 3600); rem %= 3600; dt.Minute = (uint8_t)(rem / 60); dt.Second = (uint8_t)(rem % 60); return dt; } void Timekeeping::Init(uint16_t Year, uint8_t Month, uint8_t Day, uint8_t Hour, uint8_t Minute, uint8_t Second) { g_bootEpoch = DateToEpoch(Year, Month, Day, Hour, Minute, Second); int64_t absoluteOffset = g_tzOffsetMinutes; char sign = '+'; if (absoluteOffset < 0) { sign = '-'; absoluteOffset = -absoluteOffset; } int offH = (int)(absoluteOffset / 60); int offM = (int)(absoluteOffset % 60); auto log = Kt::KernelLogStream(INFO, "Timekeeping Service"); log << "Time zone: UTC" << sign << offH; if (offM != 0) log << ":" << (offM < 10 ? "0" : "") << offM; } int64_t Timekeeping::GetUnixTimestamp() { return g_bootEpoch + (int64_t)(Timekeeping::GetMilliseconds() / 1000); } int64_t Timekeeping::DateToUnixTimestamp(int year, int month, int day, int hour, int minute, int second) { if (year < 1970 || month < 1 || month > 12 || day < 1 || day > 31) return 0; if (hour < 0 || hour > 23 || minute < 0 || minute > 59 || second < 0 || second > 60) return 0; return DateToEpoch(year, month, day, hour, minute, second); } bool Timekeeping::SetUnixTimestamp(int64_t unixSeconds) { if (unixSeconds < 0 || unixSeconds > 4102444799LL) return false; g_bootEpoch = unixSeconds - (int64_t)(Timekeeping::GetMilliseconds() / 1000); return true; } Timekeeping::DateTime Timekeeping::GetDateTime() { return EpochToDate(GetUnixTimestamp() + (int64_t)g_tzOffsetMinutes * 60); } void Timekeeping::SetTZOffset(int totalMinutes) { g_tzOffsetMinutes = totalMinutes; } int Timekeeping::GetTZOffset() { return g_tzOffsetMinutes; }