diff --git a/src/Mount.cpp b/src/Mount.cpp index 20429bd9..29f0aa7e 100644 --- a/src/Mount.cpp +++ b/src/Mount.cpp @@ -6,6 +6,7 @@ #include "EndSwitches.hpp" #include "Mount.hpp" #include "Sidereal.hpp" +#include "core/CalendarMath.hpp" #include "libs/MappedDict/MappedDict.hpp" PUSH_NO_WARNINGS @@ -4239,13 +4240,16 @@ DayTime Mount::calculateLst() { DayTime timeUTC = getUtcTime(); LocalDate localDate = getLocalDate(); - DayTime lst = Sidereal::calculateByDateAndTime(longitude().getTotalHours(), localDate.year, localDate.month, localDate.day, &timeUTC); + // The sidereal formula needs the UTC date, which differs from the local date for |UTC offset| hours each day. + core::CalendarDate utcDate + = core::localToUtcDate({localDate.year, localDate.month, localDate.day}, getLocalTime().getTotalSeconds(), _localUtcOffset); + DayTime lst = Sidereal::calculateByDateAndTime(longitude().getTotalHours(), utcDate.year, utcDate.month, utcDate.day, &timeUTC); // LOG(DEBUG_INFO, - // "[MOUNT]: Calculating LST. UTC time: %s. Date: %d-%d-%d. Longitude: %s", + // "[MOUNT]: Calculating LST. UTC time: %s. UTC date: %d-%d-%d. Longitude: %s", // timeUTC.ToString(), - // localDate.year, - // localDate.month, - // localDate.day, + // utcDate.year, + // utcDate.month, + // utcDate.day, // longitude().ToString()); // LOG(DEBUG_INFO, "[MOUNT]: LST is: %s", lst.ToString()); return lst; diff --git a/src/core/CalendarMath.cpp b/src/core/CalendarMath.cpp index 17182235..fb6b96dd 100644 --- a/src/core/CalendarMath.cpp +++ b/src/core/CalendarMath.cpp @@ -64,4 +64,22 @@ CalendarDate addDays(CalendarDate date, long days) return date; } +CalendarDate localToUtcDate(CalendarDate localDate, long localSecondsOfDay, int utcOffsetHours) +{ + long utcSeconds = localSecondsOfDay - utcOffsetHours * 3600L; + long dayShift = 0; + while (utcSeconds < 0) + { + utcSeconds += 86400L; + dayShift--; + } + while (utcSeconds >= 86400L) + { + utcSeconds -= 86400L; + dayShift++; + } + + return addDays(localDate, dayShift); +} + } // namespace core diff --git a/src/core/CalendarMath.hpp b/src/core/CalendarMath.hpp index 1e0ffa79..9a8fcf6f 100644 --- a/src/core/CalendarMath.hpp +++ b/src/core/CalendarMath.hpp @@ -14,4 +14,9 @@ struct CalendarDate { /// Handles month-length tables and leap-year logic including century rules. CalendarDate addDays(CalendarDate date, long days); +/// Convert a local calendar date to the UTC calendar date. +/// localSecondsOfDay is the local time of day in seconds since local midnight (0..86399). +/// utcOffsetHours is the local offset from UTC (local = UTC + offset). +CalendarDate localToUtcDate(CalendarDate localDate, long localSecondsOfDay, int utcOffsetHours); + } // namespace core diff --git a/unit_tests/test_core/test_calendar.cpp b/unit_tests/test_core/test_calendar.cpp index db2c25de..ece0a1c6 100644 --- a/unit_tests/test_core/test_calendar.cpp +++ b/unit_tests/test_core/test_calendar.cpp @@ -101,3 +101,83 @@ TEST(CalendarMathTest, NegativeDays) EXPECT_EQ(12, result.month); EXPECT_EQ(31, result.day); } + +TEST(CalendarMathTest, LocalToUtcDateEastAfterMidnight) +{ + // UTC+2, local 2026-10-14 01:30 is UTC 2026-10-13 23:30 + CalendarDate result = core::localToUtcDate({2026, 10, 14}, 1 * 3600L + 30 * 60L, 2); + EXPECT_EQ(2026, result.year); + EXPECT_EQ(10, result.month); + EXPECT_EQ(13, result.day); +} + +TEST(CalendarMathTest, LocalToUtcDateEastSameDay) +{ + // UTC+2, local 2026-10-14 03:30 is UTC 2026-10-14 01:30 + CalendarDate result = core::localToUtcDate({2026, 10, 14}, 3 * 3600L + 30 * 60L, 2); + EXPECT_EQ(2026, result.year); + EXPECT_EQ(10, result.month); + EXPECT_EQ(14, result.day); +} + +TEST(CalendarMathTest, LocalToUtcDateWestEvening) +{ + // UTC-5, local 2026-01-15 20:00 is UTC 2026-01-16 01:00 + CalendarDate result = core::localToUtcDate({2026, 1, 15}, 20 * 3600L, -5); + EXPECT_EQ(2026, result.year); + EXPECT_EQ(1, result.month); + EXPECT_EQ(16, result.day); +} + +TEST(CalendarMathTest, LocalToUtcDateWestSameDay) +{ + // UTC-5, local 2026-01-15 18:59:59 is UTC 2026-01-15 23:59:59 + CalendarDate result = core::localToUtcDate({2026, 1, 15}, 18 * 3600L + 59 * 60L + 59, -5); + EXPECT_EQ(2026, result.year); + EXPECT_EQ(1, result.month); + EXPECT_EQ(15, result.day); +} + +TEST(CalendarMathTest, LocalToUtcDateUtcZero) +{ + CalendarDate result = core::localToUtcDate({2026, 10, 14}, 0, 0); + EXPECT_EQ(14, result.day); + result = core::localToUtcDate({2026, 10, 14}, 86399L, 0); + EXPECT_EQ(14, result.day); +} + +TEST(CalendarMathTest, LocalToUtcDateEastYearBoundary) +{ + // UTC+10, local 2027-01-01 05:00 is UTC 2026-12-31 19:00 + CalendarDate result = core::localToUtcDate({2027, 1, 1}, 5 * 3600L, 10); + EXPECT_EQ(2026, result.year); + EXPECT_EQ(12, result.month); + EXPECT_EQ(31, result.day); +} + +TEST(CalendarMathTest, LocalToUtcDateWestLeapDay) +{ + // UTC-8, local 2028-02-28 17:00 is UTC 2028-02-29 01:00 + CalendarDate result = core::localToUtcDate({2028, 2, 28}, 17 * 3600L, -8); + EXPECT_EQ(2028, result.year); + EXPECT_EQ(2, result.month); + EXPECT_EQ(29, result.day); +} + +TEST(CalendarMathTest, LocalToUtcDateWestYearBoundary) +{ + // UTC-12, local 2026-12-31 12:00 is UTC 2027-01-01 00:00 + CalendarDate result = core::localToUtcDate({2026, 12, 31}, 12 * 3600L, -12); + EXPECT_EQ(2027, result.year); + EXPECT_EQ(1, result.month); + EXPECT_EQ(1, result.day); +} + +TEST(CalendarMathTest, LocalToUtcDateExactUtcMidnight) +{ + // UTC+2, local 02:00 is exactly UTC 00:00 on the same date; 01:59:59 is still the day before + CalendarDate result = core::localToUtcDate({2026, 10, 14}, 2 * 3600L, 2); + EXPECT_EQ(14, result.day); + result = core::localToUtcDate({2026, 10, 14}, 2 * 3600L - 1, 2); + EXPECT_EQ(13, result.day); +}