diff --git a/config/rel_slices.yml b/config/rel_slices.yml index 07c22600..9f8f3737 100644 --- a/config/rel_slices.yml +++ b/config/rel_slices.yml @@ -17,6 +17,15 @@ graph.c: .text: [0x80405518, 0x80405EC8] .data: [0x8065ECA8, 0x8065ECB0] .bss: [0x812F31E8, 0x812F3560] +lb_rtc.c: + .text: [0x80406480, 0x8040752C] + .rodata: [0x806436F8, 0x806437A0] + .data: [0x8065ECD0, 0x8065ECD8] + .bss: [0x812F4CB0, 0x812F4CC0] +lb_reki.c: + .text: [0x8040752C, 0x80407AE8] + .rodata: [0x806437A0, 0x806437C0] + .data: [0x8065ECD8, 0x8065F110] zurumode.c: .text: [0x8040eb38, 0x8040f008] .bss: [0x812f9670, 0x812f9680] diff --git a/include/dolphin/os/OSTime.h b/include/dolphin/os/OSTime.h index 52d416f1..46472dd3 100644 --- a/include/dolphin/os/OSTime.h +++ b/include/dolphin/os/OSTime.h @@ -10,9 +10,42 @@ extern "C" { typedef s64 OSTime; typedef u32 OSTick; +u32 __busclock AT_ADDRESS(0x800000F8); + +#define OS_BUS_CLOCK __busclock + +#define OS_TIMER_CLOCK (OS_BUS_CLOCK / 4) + +#define OSTicksToCycles(ticks) (((ticks) * ((OS_CORE_CLOCK * 2) / OS_TIMER_CLOCK)) / 2) +#define OSTicksToSeconds(ticks) ((ticks) / OS_TIMER_CLOCK) +#define OSTicksToMilliseconds(ticks) ((ticks) / (OS_TIMER_CLOCK / 1000)) +#define OSTicksToMicroseconds(ticks) (((ticks)*8) / (OS_TIMER_CLOCK / 125000)) +#define OSTicksToNanoseconds(ticks) (((ticks)*8000) / (OS_TIMER_CLOCK / 125000)) +#define OSSecondsToTicks(sec) ((sec)*OS_TIMER_CLOCK) +#define OSMillisecondsToTicks(msec) ((msec) * (OS_TIMER_CLOCK / 1000)) +#define OSMicrosecondsToTicks(usec) (((usec) * (OS_TIMER_CLOCK / 125000)) / 8) +#define OSNanosecondsToTicks(nsec) (((nsec) * (OS_TIMER_CLOCK / 125000)) / 8000) + OSTime OSGetTime(void); OSTick OSGetTick(void); +typedef struct OSCalendarTime_s { + int sec; + int min; + int hour; + int mday; + int mon; + int year; + int wday; + int yday; + + int msec; + int usec; +} OSCalendarTime; + +OSTime OSCalendarTimeToTicks(OSCalendarTime* td); +void OSTicksToCalendarTime(OSTime ticks, OSCalendarTime* td); + #ifdef __cplusplus } #endif diff --git a/include/lb_reki.h b/include/lb_reki.h new file mode 100644 index 00000000..4b980f19 --- /dev/null +++ b/include/lb_reki.h @@ -0,0 +1,35 @@ +#ifndef LB_REKI_H +#define LB_REKI_H + +#include "types.h" +#include "lb_rtc.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define lbRk_YEAR_MIN GAME_YEAR_MIN +#define lbRk_YEAR_MAX GAME_YEAR_MAX +#define lbRk_YEAR_NUM ((lbRk_YEAR_MAX - lbRk_YEAR_MIN) + 1) + +#define lbRk_KYUU_MONTH_START 1 +#define lbRk_KYUU_MONTH_END 12 +#define lbRk_KYUU_LEAP_MONTH 13 + +#define lbRk_HARVEST_MOON_YEAR_MIN 2002 +#define lbRk_HARVEST_MOON_YEAR_MAX 2030 +#define lbRk_HARVEST_MOON_YEAR_NUM ((lbRk_HARVEST_MOON_YEAR_MAX - lbRk_HARVEST_MOON_YEAR_MIN) + 1) + +#define lbRk_KYUU_DAY_START 1 + +extern int lbRk_ToSeiyouReki(lbRTC_ymd_t* seiyo_ymd, const lbRTC_ymd_t* kyuu_ymd); +extern int lbRk_ToKyuuReki(lbRTC_ymd_t* kyuu_ymd, const lbRTC_ymd_t* seiyo_ymd); +extern int lbRk_VernalEquinoxDay(int year); +extern int lbRk_AutumnalEquinoxDay(int year); +extern void lbRk_HarvestMoonDay(lbRTC_ymd_t* harvest_moon_day, int year); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/include/lb_rtc.h b/include/lb_rtc.h new file mode 100644 index 00000000..7fbfaf0a --- /dev/null +++ b/include/lb_rtc.h @@ -0,0 +1,147 @@ +#ifndef LB_RTC_H +#define LB_RTC_H + +#include "types.h" +#include "dolphin/os/OSTime.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* TODO: do these have a better header? */ +#define GAME_YEAR_MIN 2000 /* Minimum year supported by the game */ +#define GAME_YEAR_MAX 2032 /* Maximum year supported by the game */ + +#define lbRTC_YEAR_MIN 1901 +#define lbRTC_YEAR_MAX 2099 + +typedef u8 lbRTC_sec_t; +typedef u8 lbRTC_min_t; +typedef u8 lbRTC_hour_t; +typedef u8 lbRTC_day_t; +typedef u8 lbRTC_weekday_t; +typedef u8 lbRTC_month_t; +typedef u16 lbRTC_year_t; + +typedef struct lbRTC_datetime_s { + lbRTC_sec_t sec; + lbRTC_min_t min; + lbRTC_hour_t hour; + lbRTC_day_t day; + lbRTC_weekday_t weekday; + lbRTC_month_t month; + lbRTC_year_t year; +} lbRTC_time_c; /* Name leaked in lbRTC_time_c_save_data_check */ + +typedef struct lbRTC_ymd_s { + lbRTC_year_t year; + lbRTC_month_t month; + lbRTC_day_t day; +} lbRTC_ymd_t; /* Name leaked in mTM_ymd_2_time */ + +enum WEEKDAYS { + lbRTC_WEEKDAYS_BEGIN = 0, + lbRTC_SUNDAY = lbRTC_WEEKDAYS_BEGIN, + lbRTC_MONDAY, + lbRTC_TUESDAY, + lbRTC_WEDNESDAY, + lbRTC_THURSDAY, + lbRTC_FRIDAY, + lbRTC_SATURDAY, + lbRTC_WEEK, + lbRTC_WEEKDAYS_MAX = lbRTC_WEEK +}; + +enum MONTHS { + lbRTC_MONTHS_BEGIN = 0, + lbRTC_JANUARY = 1, + lbRTC_FEBRUARY, + lbRTC_MARCH, + lbRTC_APRIL, + lbRTC_MAY, + lbRTC_JUNE, + lbRTC_JULY, + lbRTC_AUGUST, + lbRTC_SEPTEMBER, + lbRTC_OCTOBER, + lbRTC_NOVEMBER, + lbRTC_DECEMBER, + lbRTC_MONTHS_MAX = lbRTC_DECEMBER +}; + +enum RTC_EQUALITY { + lbRTC_LESS = -1, + lbRTC_EQUAL = 0, + lbRTC_OVER = 1 +}; + +enum RTC_EQUALITY_FLAGS { + lbRTC_CHECK_NONE = 0, /* 0x00 */ + + lbRTC_CHECK_SECONDS = 1 << 0, /* 0x01 */ + lbRTC_CHECK_MINUTES = 1 << 1, /* 0x02 */ + lbRTC_CHECK_HOURS = 1 << 2, /* 0x04 */ + lbRTC_CHECK_WEEKDAYS = 1 << 3, /* 0x08 */ + lbRTC_CHECK_DAYS = 1 << 4, /* 0x10 */ + lbRTC_CHECK_MONTHS = 1 << 5, /* 0x20 */ + lbRTC_CHECK_YEARS = 1 << 6, /* 0x40 */ + + /* 0x7F */ + lbRTC_CHECK_ALL = lbRTC_CHECK_SECONDS | + lbRTC_CHECK_MINUTES | + lbRTC_CHECK_HOURS | + lbRTC_CHECK_WEEKDAYS | + lbRTC_CHECK_DAYS | + lbRTC_CHECK_MONTHS | + lbRTC_CHECK_YEARS +}; + +extern OSTime lbRTC_HardTime(); +extern int lbRTC_IsAbnormal(); +extern void lbRTC_Sampling(); +extern void lbRTC_SetTime(lbRTC_time_c* time); +extern void lbRTC_GetTime(lbRTC_time_c* time); +extern lbRTC_day_t lbRTC_GetDaysByMonth(lbRTC_year_t year, lbRTC_month_t month); +extern int lbRTC_IsEqualDate( + lbRTC_year_t y0, lbRTC_month_t m0, lbRTC_day_t d0, + lbRTC_year_t y1, lbRTC_month_t m1, lbRTC_day_t d1 +); +extern int lbRTC_IsEqualTime(const lbRTC_time_c* t0, const lbRTC_time_c* t1, int flags); +extern int lbRTC_IsOverTime(const lbRTC_time_c* t0, const lbRTC_time_c* t1); +// extern int lbRTC_IsJustAtRTC(const lbRTC_time_c* time, int check_flags); +extern int lbRTC_IsOverRTC(const lbRTC_time_c* time); +extern int lbRTC_IntervalTime(const lbRTC_time_c* time0, const lbRTC_time_c* time1); +extern int lbRTC_GetIntervalDays(const lbRTC_time_c* t0, const lbRTC_time_c* t1); +extern int lbRTC_GetIntervalDays2(const lbRTC_ymd_t* ymd0, const lbRTC_ymd_t* ymd1); +extern void lbRTC_Add_YY(lbRTC_time_c* time, int year); +extern void lbRTC_Add_MM(lbRTC_time_c* time, int month); +extern void lbRTC_Add_DD(lbRTC_time_c* time, int day); +extern void lbRTC_Add_hh(lbRTC_time_c* time, int hour); +extern void lbRTC_Add_mm(lbRTC_time_c* time, int min); +extern void lbRTC_Add_ss(lbRTC_time_c* time, int sec); +extern void lbRTC_Add_Date(lbRTC_time_c* time, const lbRTC_time_c* add_time); +extern void lbRTC_Sub_YY(lbRTC_time_c* time, int year); +extern void lbRTC_Sub_MM(lbRTC_time_c* time, int month); +extern void lbRTC_Sub_DD(lbRTC_time_c* time, int days); +extern void lbRTC_Sub_hh(lbRTC_time_c* time, int hour); +extern void lbRTC_Sub_mm(lbRTC_time_c* time, int min); +extern void lbRTC_Sub_ss(lbRTC_time_c* time, int sec); +// extern void lbRTC_Sub_Date(lbRTC_time_c* time, const lbRTC_time_c* sub_time) +extern lbRTC_weekday_t lbRTC_Week(lbRTC_year_t year, lbRTC_month_t month, lbRTC_day_t day); +extern void lbRTC_TimeCopy(lbRTC_time_c* dst, const lbRTC_time_c* src); +extern int lbRTC_IsValidTime(const lbRTC_time_c* time); +extern int lbRTC_time_c_save_data_check(const lbRTC_time_c* time); +extern int lbRTC_Weekly_day(lbRTC_year_t year, lbRTC_month_t month, int weeks, int weekday); + +#define lbRTC_HOURS_PER_DAY 24 +#define lbRTC_MINUTES_PER_HOUR 60 +#define lbRTC_SECONDS_PER_MINUTE 60 + +#define lbRTC_IS_LEAPYEAR(year) \ + (((year % 4) == 0 && ((year % 100) != 0)) || ((year % 400) == 0)) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/include/m_actor_type.h b/include/m_actor_type.h new file mode 100644 index 00000000..e8727eb5 --- /dev/null +++ b/include/m_actor_type.h @@ -0,0 +1,16 @@ +#ifndef M_ACTOR_TYPE_H +#define M_ACTOR_TYPE_H + +#include "types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef u16 mActor_name_t; + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/include/m_common_data.h b/include/m_common_data.h new file mode 100644 index 00000000..a8cf95d2 --- /dev/null +++ b/include/m_common_data.h @@ -0,0 +1,67 @@ +#ifndef M_COMMON_DATA_H +#define M_COMMON_DATA_H + +#include "types.h" +#include "m_actor_type.h" +#include "lb_rtc.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct time_s { + u32 season; + u32 term_idx; + s16 bg_item_profile; + s16 bg_item_bank; + int now_sec; + lbRTC_time_c rtc_time; + s16 rad_min; /* clock hand radial position for mins */ + s16 rad_hour; /* clock hand radial position for hours */ + u8 time_signal; + u8 under_sec; + u8 disp; + u8 rtc_crashed; + int rtc_enabled; + int add_sec; + int add_idx; +} Time_c; + +typedef struct Save_s { + u8 _tmp0[0x22528]; + OSTime time_delta; + u8 _tmp1[0x3AD0]; +} Save_t; + +typedef union save_u { + Save_t save; + u8 raw[0x26000]; /* Temp to force length */ +} Save; + +typedef struct common_data_s { + /* 0x000000 */ Save save; + /* 0x026000 */ u8 game_1_patu; + /* 0x026001 */ u8 field_type; + /* 0x026002 */ u8 field_draw_type; + /* 0x026003 */ u8 player_no; + /* 0x026004 */ int last_scene_no; + /* 0x026008 */ int player_data_mode; + /* 0x02600C */ u8 _clip[0x104]; /* Temporary, clip is a struct with size 0x104 */ + /* 0x026110 */ Time_c time; +} common_data_t; + +extern common_data_t common_data; + +#define Common_Get(name) (common_data.name) +#define Common_GetPointer(name) (&common_data.name) +#define Common_Set(name, value) (common_data.name = (value)) + +#define Save_Get(name) (Common_Get(save.save.name)) +#define Save_GetPointer(name) (Common_GetPointer(save.save.name)) +#define Save_Set(name, value) (Common_Set(save.save.name, value)) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/rel/lb_reki.c b/rel/lb_reki.c new file mode 100644 index 00000000..c99f2e57 --- /dev/null +++ b/rel/lb_reki.c @@ -0,0 +1,409 @@ +/* + * lb_reki.c + * + * This source file contains a set of functions used to convert dates + * between the Gregorian calendar (seiyo reki, 西洋暦) and the old Japanese + * lunisolar calendar (kyuu reki, 旧暦). The functions also handle special + * date calculations related to the Japanese calendar, such as Vernal Equinox Day, + * Autumnal Equinox Day, and Harvest Moon Day. + * + * Functions in this file include: + * + * - lbRk_SeirekiDays: Calculate the number of days in a given month of a given year in the Gregorian calendar. + * - lbRk_ToSeiyoMonthAndDay: Convert a kyuureki year and month to the corresponding Gregorian month and day. + * - lbRk_KyuurekiLeapDays: Calculate the number of leap days in a given kyuureki year. + * - lbRk_IsKyuurekiLeapYear: Check if a kyuureki year is a leap year. + * - lbRk_IsLeapMonth: Check if a kyuureki month is a leap month. + * - lbRk_IsLeapOnNextMonth: Check if a leap month occurs after the given kyuureki month. + * - lbRk_KyuurekiDays: Calculate the number of days in a given kyuureki month of a given year. + * - lbRk_ToSeiyouReki: Convert a kyuureki date to the corresponding Gregorian date. + * - lbRk_ToKyuuReki: Convert a Gregorian date to the corresponding kyuureki date. + * - lbRk_VernalEquinoxDay: Calculate the Vernal Equinox Day for a given year. + * - lbRk_AutumnalEquinoxDay: Calculate the Autumnal Equinox Day for a given year. + * - lbRk_HarvestMoonDay: Calculate the Harvest Moon Day for a given year. + */ + +#include "lb_reki.h" + +#include "types.h" + +typedef struct lbRk_date_s { + lbRTC_month_t month; + lbRTC_day_t day; +} lbRk_date_t; + +/** + * @brief Gets the number of days in a given month of the Seiyo Reki (Gregorian calendar). + * + * @param year Year value. + * @param month Month value. + * @return The number of days in the specified month of the given year. + */ +static int lbRk_SeirekiDays(int year, int month) { + // Table containing the number of days for each month in standard and leap years in the Seiyo Reki (Gregorian calendar) + static lbRTC_day_t t_seiyo_days_tbl[][lbRTC_MONTHS_MAX + 1] = { + {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}, /* Standard days for each month */ + {0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31} /* Days for each month during leap year */ + }; + + int idx = 0; + if ((year % 4) == 0) { + idx = 1; + } + + return t_seiyo_days_tbl[idx][month]; +} + +/* Convesion table which gets the beginning of a Kyuu Reki (Japanese lunisolar) + date in Seiyo Reki (Gregoran calendar) date. */ +static lbRk_date_t l_lbRk_ConvertTable[lbRk_YEAR_NUM][lbRk_KYUU_LEAP_MONTH] = { + /* 2000 */ {{2, 5}, {3, 6}, {4, 5}, {5, 4}, {6, 2}, {7, 2}, {7, 31}, {8, 29}, { 9, 28}, {10, 27}, {11, 26}, {12, 26}, { 0, 0}}, + /* 2001 */ {{1, 24}, {2, 23}, {3, 25}, {4, 24}, {6, 21}, {7, 21}, {8, 19}, {9, 17}, {10, 17}, {11, 15}, {12, 15}, { 1, 13}, { 5, 23}}, + /* 2002 */ {{2, 12}, {3, 14}, {4, 13}, {5, 12}, {6, 11}, {7, 10}, {8, 9}, {9, 7}, {10, 6}, {11, 5}, {12, 4}, { 1, 3}, { 0, 0}}, + /* 2003 */ {{2, 1}, {3, 3}, {4, 2}, {5, 1}, {5, 31}, {6, 30}, {7, 29}, {8, 28}, { 9, 26}, {10, 25}, {11, 24}, {12, 23}, { 0, 0}}, + /* 2004 */ {{1, 22}, {2, 20}, {4, 19}, {5, 19}, {6, 18}, {7, 17}, {8, 16}, {9, 14}, {10, 14}, {11, 12}, {12, 12}, { 1, 10}, { 3, 21}}, + /* 2005 */ {{2, 9}, {3, 10}, {4, 9}, {5, 8}, {6, 7}, {7, 6}, {8, 5}, {9, 4}, {10, 3}, {11, 2}, {12, 2}, {12, 31}, { 0, 0}}, + /* 2006 */ {{1, 29}, {2, 28}, {3, 29}, {4, 28}, {5, 27}, {6, 26}, {7, 25}, {9, 22}, {10, 22}, {11, 21}, {12, 20}, { 1, 19}, { 8, 24}}, + /* 2007 */ {{2, 18}, {3, 19}, {4, 17}, {5, 17}, {6, 15}, {7, 14}, {8, 13}, {9, 11}, {10, 11}, {11, 10}, {12, 10}, { 1, 8}, { 0, 0}}, + /* 2008 */ {{2, 7}, {3, 8}, {4, 6}, {5, 5}, {6, 4}, {7, 3}, {8, 1}, {8, 31}, { 9, 29}, {10, 29}, {11, 28}, {12, 27}, { 0, 0}}, + /* 2009 */ {{1, 26}, {2, 25}, {3, 27}, {4, 25}, {5, 24}, {7, 22}, {8, 20}, {9, 19}, {10, 18}, {11, 17}, {12, 16}, { 1, 15}, { 6, 23}}, + /* 2010 */ {{2, 14}, {3, 16}, {4, 14}, {5, 14}, {6, 12}, {7, 12}, {8, 10}, {9, 8}, {10, 8}, {11, 6}, {12, 6}, { 1, 4}, { 0, 0}}, + /* 2011 */ {{2, 3}, {3, 5}, {4, 3}, {5, 3}, {6, 2}, {7, 1}, {7, 31}, {8, 29}, { 9, 27}, {10, 27}, {11, 25}, {12, 25}, { 0, 0}}, + /* 2012 */ {{1, 23}, {2, 22}, {3, 22}, {5, 21}, {6, 20}, {7, 19}, {8, 18}, {9, 16}, {10, 15}, {11, 14}, {12, 13}, { 1, 12}, { 4, 21}}, + /* 2013 */ {{2, 10}, {3, 12}, {4, 10}, {5, 10}, {6, 9}, {7, 8}, {8, 7}, {9, 5}, {10, 5}, {11, 3}, {12, 3}, { 1, 1}, { 0, 0}}, + /* 2014 */ {{1, 31}, {3, 1}, {3, 31}, {4, 29}, {5, 29}, {6, 27}, {7, 27}, {8, 25}, { 9, 24}, {11, 22}, {12, 22}, { 1, 20}, {10, 24}}, + /* 2015 */ {{2, 19}, {3, 20}, {4, 19}, {5, 18}, {6, 16}, {7, 16}, {8, 14}, {9, 13}, {10, 13}, {11, 12}, {12, 11}, { 1, 10}, { 0, 0}}, + /* 2016 */ {{2, 8}, {3, 9}, {4, 7}, {5, 7}, {6, 5}, {7, 4}, {8, 3}, {9, 1}, {10, 1}, {10, 31}, {11, 29}, {12, 29}, { 0, 0}}, + /* 2017 */ {{1, 28}, {2, 26}, {3, 28}, {4, 26}, {5, 26}, {7, 23}, {8, 22}, {9, 20}, {10, 20}, {11, 18}, {12, 18}, { 1, 17}, { 6, 24}}, + /* 2018 */ {{2, 16}, {3, 17}, {4, 16}, {5, 15}, {6, 14}, {7, 13}, {8, 11}, {9, 10}, {10, 9}, {11, 8}, {12, 27}, { 1, 6}, { 0, 0}}, + /* 2019 */ {{2, 5}, {3, 7}, {4, 5}, {5, 5}, {6, 3}, {7, 3}, {8, 1}, {8, 30}, { 9, 29}, {10, 28}, {11, 27}, {12, 26}, { 0, 0}}, + /* 2020 */ {{1, 25}, {2, 24}, {3, 24}, {4, 23}, {6, 21}, {7, 21}, {8, 19}, {9, 17}, {10, 17}, {11, 15}, {12, 15}, { 1, 13}, { 5, 23}}, + /* 2021 */ {{2, 12}, {3, 13}, {4, 12}, {5, 12}, {6, 10}, {7, 10}, {8, 8}, {9, 7}, {10, 6}, {11, 5}, {12, 4}, { 1, 3}, { 0, 0}}, + /* 2022 */ {{2, 1}, {3, 3}, {4, 1}, {5, 1}, {5, 30}, {6, 29}, {7, 29}, {8, 27}, { 9, 26}, {10, 25}, {11, 24}, {12, 23}, { 0, 0}}, + /* 2023 */ {{1, 22}, {2, 20}, {3, 22}, {5, 20}, {6, 18}, {7, 18}, {8, 16}, {9, 15}, {10, 15}, {11, 13}, {12, 13}, { 1, 11}, { 4, 20}}, + /* 2024 */ {{2, 10}, {3, 10}, {4, 9}, {5, 8}, {6, 6}, {7, 6}, {8, 4}, {9, 3}, {10, 3}, {11, 1}, {12, 1}, {12, 31}, { 0, 0}}, + /* 2025 */ {{1, 29}, {2, 28}, {3, 29}, {4, 28}, {5, 27}, {6, 25}, {8, 23}, {9, 22}, {10, 21}, {11, 20}, {12, 20}, { 1, 19}, { 7, 25}}, + /* 2026 */ {{2, 17}, {3, 19}, {4, 17}, {5, 17}, {6, 15}, {7, 14}, {8, 13}, {9, 11}, {10, 11}, {11, 9}, {12, 9}, { 1, 8}, { 0, 0}}, + /* 2027 */ {{2, 7}, {3, 8}, {4, 7}, {5, 6}, {6, 5}, {7, 4}, {8, 2}, {9, 1}, { 9, 30}, {10, 29}, {11, 28}, {12, 28}, { 0, 0}}, + /* 2028 */ {{1, 27}, {2, 25}, {3, 26}, {4, 25}, {5, 24}, {7, 22}, {8, 20}, {9, 19}, {10, 18}, {11, 16}, {12, 16}, { 1, 15}, { 6, 23}}, + /* 2029 */ {{2, 13}, {3, 15}, {4, 14}, {5, 13}, {6, 12}, {7, 12}, {8, 10}, {9, 8}, {10, 8}, {11, 6}, {12, 5}, { 1, 4}, { 0, 0}}, + /* 2030 */ {{2, 3}, {3, 4}, {4, 3}, {5, 2}, {6, 1}, {7, 1}, {7, 30}, {8, 29}, { 9, 27}, {10, 27}, {11, 25}, {12, 25}, { 0, 0}}, + /* 2031 */ {{1, 23}, {2, 22}, {3, 23}, {4, 22}, {6, 20}, {7, 19}, {8, 18}, {9, 17}, {10, 16}, {11, 15}, {12, 14}, { 1, 13}, { 5, 21}}, + /* 2032 */ {{2, 11}, {3, 12}, {4, 10}, {5, 9}, {6, 8}, {7, 7}, {8, 6}, {9, 5}, {10, 4}, {11, 3}, {12, 3}, { 1, 1}, { 0, 0}}, +}; + +// The number of Japanese lunisolar leap days for a given lunisolar year. +static int l_lbRk_leapdays[lbRk_YEAR_NUM] = { + 0, 29, 0, 0, + 29, 0, 29, 0, + 0, 29, 0, 0, + 30, 0, 29, 0, + 0, 29, 0, 0, + 29, 0, 0, 30, + 0, 29, 0, 0, + 29, 0, 0, 30, + 0 +}; + +/** + * @brief Get a pointer to the lbRk_date_t structure for the Seiyo Reki (Gregorian calendar) month and day. + * + * @param year Year value. + * @param month Month value. + * @return Pointer to the lbRk_date_t structure representing the Seiyo Reki (Gregorian calendar) month and day. + */ +static lbRk_date_t* lbRk_ToSeiyoMonthAndDay(int year, int month) { + return &l_lbRk_ConvertTable[year - lbRk_YEAR_MIN][month - 1]; +} + +/** + * @brief Get the number of leap days for a given Kyuu Reki (Japanese lunisolar) year. + * + * @param year Year value. + * @return The number of leap days for the specified Kyuu Reki (Japanese lunisolar) year. + */ +static int lbRk_KyuurekiLeapDays(int year) { + return l_lbRk_leapdays[year - lbRk_YEAR_MIN]; +} + +/** + * @brief Check if a given Kyuu Reki (Japanese lunisolar) year is a leap year. + * + * @param year Year value. + * @return Non-zero (TRUE) if the given Kyuu Reki (Japanese lunisolar) year is a leap year, zero (FALSE) otherwise. + */ +static int lbRk_IsKyuurekiLeapYear(int year) { + return l_lbRk_leapdays[year - lbRk_YEAR_MIN] != 0; +} + +/** + * @brief Check if a given month is a leap month in the Kyuu Reki (Japanese lunisolar) calendar. + * + * @param month Month value. + * @return Non-zero (TRUE) if the given month is a leap month, zero (FALSE) otherwise. + */ +static int lbRk_IsLeapMonth(int month) { + return month == lbRk_KYUU_LEAP_MONTH; +} + +/** + * @brief Check if the leap month occurs next month in the Kyuu Reki (Japanese lunisolar) calendar. + * + * @param year Year value. + * @param month Month value. + * @return Non-zero (TRUE) if the leap month occurs next month, zero (FALSE) otherwise. + */ +static int lbRk_IsLeapOnNextMonth(int year, int month) { + lbRk_date_t* seiyo_monthday = lbRk_ToSeiyoMonthAndDay(year, month); + lbRk_date_t* convert_monthday = &l_lbRk_ConvertTable[year - lbRk_YEAR_MIN][lbRTC_MONTHS_MAX]; + + // Check if the leap month occurs next month by comparing the month values + // in the Seiyo Reki (Gregorian calendar) + return !(convert_monthday->month != seiyo_monthday->month + 1); +} + +/** + * Calculates the number of days in a given month of the Japanese lunisolar calendar (kyuureki) + * for a given year and month. + * + * @param year The year in the kyuureki calendar. + * @param month The month in the kyuureki calendar. + * @return The number of days in the specified month of the Japanese lunisolar calendar. + */ +static int lbRk_KyuurekiDays(int year, int month) { + int days; + lbRk_date_t* next_seiyo_month_day; + + // Get the corresponding Seiyo (Gregorian) month and day for the given kyuureki year and month + lbRk_date_t* seiyo_monthday = lbRk_ToSeiyoMonthAndDay(year, month); + + // If the month is a leap month, return the number of leap days for the year + if (lbRk_IsLeapMonth(month) == TRUE) { + return lbRk_KyuurekiLeapDays(year); + } + + // Determine the Seiyo (Gregorian) month and day for the next month in the kyuureki calendar + if (month != lbRTC_MONTHS_MAX) { + if (lbRk_IsLeapOnNextMonth(year, month) == TRUE) { + next_seiyo_month_day = lbRk_ToSeiyoMonthAndDay(year, lbRk_KYUU_LEAP_MONTH); + } else { + next_seiyo_month_day = lbRk_ToSeiyoMonthAndDay(year, month + 1); + } + } else { + next_seiyo_month_day = lbRk_ToSeiyoMonthAndDay(year + 1, 1); + } + + // Calculate the number of days in the Kyuu (Japanese lunisolar) month + if (seiyo_monthday->month == next_seiyo_month_day->month) { + days = next_seiyo_month_day->day - seiyo_monthday->day; + } else { + if (seiyo_monthday->month < month) { + year += 1; + } + + days = lbRk_SeirekiDays(year, seiyo_monthday->month) - seiyo_monthday->day; + if ((next_seiyo_month_day->month - seiyo_monthday->month) >= 2) { + days += lbRk_SeirekiDays(year, seiyo_monthday->month + 1); + } + days += next_seiyo_month_day->day; + } + return days; +} + + +/** + * Converts a date from the Japanese lunisolar calendar (kyuureki) to the Gregorian calendar (seiyo reki). + * + * @param seiyo_ymd The output date in the Gregorian calendar. + * @param kyuu_ymd The input date in the kyuureki calendar. + * @return TRUE if the conversion is successful, FALSE if the input date is invalid. + */ +extern int lbRk_ToSeiyouReki(lbRTC_ymd_t* seiyo_ymd, const lbRTC_ymd_t* kyuu_ymd) { + int seireki_days; + int year; + int month; + int day; + lbRk_date_t* seiyo_monthday; + + // Validate the input kyuureki date + if ((kyuu_ymd->year < 1) || (kyuu_ymd->year > lbRk_YEAR_MAX)) { + return FALSE; + } + if (kyuu_ymd->month > lbRk_KYUU_LEAP_MONTH) { + return FALSE; + } + if (lbRk_IsKyuurekiLeapYear(kyuu_ymd->year) == FALSE && kyuu_ymd->month >= lbRk_KYUU_LEAP_MONTH) { + return FALSE; + } + if (lbRk_KyuurekiDays(kyuu_ymd->year, kyuu_ymd->month) < kyuu_ymd->day) { + return FALSE; + } + + // Get the corresponding Seiyo (Gregorian) month and day for the given kyuureki year and month + seiyo_monthday = lbRk_ToSeiyoMonthAndDay(kyuu_ymd->year, kyuu_ymd->month); + month = seiyo_monthday->month; + year = kyuu_ymd->year; + day = seiyo_monthday->day + kyuu_ymd->day - 1; + + // Adjust the Gregorian date based on the number of days in the Seiyo (Gregorian) month + seireki_days = lbRk_SeirekiDays(year, month); + if (day > seireki_days) { + month++; + day -= seireki_days; + if (month > lbRTC_MONTHS_MAX) { + month = 1; + } + } + + // Adjust the Gregorian year if necessary + if (month < kyuu_ymd->month && kyuu_ymd->month != lbRk_KYUU_LEAP_MONTH) { + year++; + } + + // Set the output Gregorian date + seiyo_ymd->year = year; + seiyo_ymd->month = month; + seiyo_ymd->day = day; + + return TRUE; +} + +/** + * Converts a date from the Gregorian calendar (seiyo reki) to the Japanese lunisolar calendar (kyuureki). + * + * @param kyuu_ymd The output date in the kyuureki calendar. + * @param seiyo_ymd The input date in the Gregorian calendar. + * @return TRUE if the conversion is successful, FALSE if the input date is invalid. + */ +extern int lbRk_ToKyuuReki(lbRTC_ymd_t* kyuu_ymd, const lbRTC_ymd_t* seiyo_ymd) { + lbRTC_ymd_t kyuu_date = (lbRTC_ymd_t){lbRk_YEAR_MIN, lbRk_KYUU_MONTH_START, lbRk_KYUU_DAY_START}; + lbRTC_ymd_t seyio_date; + + while (TRUE) { + // Attempt to convert the current kyuureki date to Gregorian date + if (lbRk_ToSeiyouReki(&seyio_date, &kyuu_date) == FALSE) { + return FALSE; + } + + // Check if the converted Gregorian date matches the input seiyo_ymd + if (seyio_date.day == seiyo_ymd->day && seyio_date.month == seiyo_ymd->month && seyio_date.year == seiyo_ymd->year) { + kyuu_ymd->day = kyuu_date.day; + kyuu_ymd->month = kyuu_date.month; + kyuu_ymd->year = kyuu_date.year; + return TRUE; + } + + // Increment the kyuureki day + kyuu_date.day++; + + // If the kyuureki day exceeds the number of days in the current kyuureki month, reset the day and increment the month + if (lbRk_KyuurekiDays(kyuu_date.year, kyuu_date.month) < kyuu_date.day) { + kyuu_date.day = 1; + kyuu_date.month++; + + // Handle leap years and month transitions + if (lbRk_IsKyuurekiLeapYear(kyuu_date.year) == FALSE) { + if (kyuu_date.month > lbRk_KYUU_MONTH_END) { + kyuu_date.month = 1; + kyuu_date.year++; + if (kyuu_date.year > lbRk_YEAR_MAX) { + return FALSE; + } + } + } else if (kyuu_date.month > lbRk_KYUU_LEAP_MONTH) { + kyuu_date.month = 1; + kyuu_date.year++; + if (kyuu_date.year > lbRk_YEAR_MAX) { + return FALSE; + } + } + } + } +} + +/** + * Calculates the Vernal Equinox Day for the given year. + * + * @param year The input year. + * @return The Vernal Equinox Day for the given year. + * @remarks This function calculates the Vernal Equinox Day from 1980 thru 2099. + * It was sourced from '新こよみ便利帳 天文現象・暦計算のすべて' + * by Kouseisha Kouseikaku (ISBN4-7699-0700-1). + */ +extern int lbRk_VernalEquinoxDay(int year) { + year -= 1980; + return (int)(20.8431f + (0.242194f * (f32)year)) - (year / 4); +} + +/** + * Calculates the Autumnal Equinox Day for the given year. + * + * @param year The input year. + * @return The Autumnal Equinox Day for the given year. + * @remarks This function calculates the Autmnal Equinox Day from 1980 thru 2099. + * It was sourced from '新こよみ便利帳 天文現象・暦計算のすべて' + * by Kouseisha Kouseikaku (ISBN4-7699-0700-1). + */ +extern int lbRk_AutumnalEquinoxDay(int year) { + year -= 1980; + return (int)(23.2488f + (0.242194f * (f32)year)) - (year / 4); +} + +/** + * Calculates the Harvest Moon Day for the given year. + * + * @param harvest_moon_day The output Harvest Moon Day in the Gregorian calendar. + * @param year The input year. + */ +extern void lbRk_HarvestMoonDay(lbRTC_ymd_t* harvest_moon_day, int year) { + /* Array of precomputed Seiyo Reki (Gregorian calendar) + Havest Moon Day dates. */ + static lbRk_date_t ev_day[lbRk_HARVEST_MOON_YEAR_NUM] = { + /* 2002 */ {9, 21}, + /* 2003 */ {9, 10}, + /* 2004 */ {9, 28}, + /* 2005 */ {9, 18}, + /* 2006 */ {10, 7}, + /* 2007 */ {9, 26}, + /* 2008 */ {9, 15}, + /* 2009 */ {10, 4}, + /* 2010 */ {9, 23}, + /* 2011 */ {9, 12}, + /* 2012 */ {9, 30}, + /* 2013 */ {9, 19}, + /* 2014 */ {9, 9}, + /* 2015 */ {9, 28}, + /* 2016 */ {9, 16}, + /* 2017 */ {10, 5}, + /* 2018 */ {9, 25}, + /* 2019 */ {9, 14}, + /* 2020 */ {10, 1}, + /* 2021 */ {9, 20}, + /* 2022 */ {9, 10}, + /* 2023 */ {9, 29}, + /* 2024 */ {9, 18}, + /* 2025 */ {10, 7}, + /* 2026 */ {9, 26}, + /* 2027 */ {9, 15}, + /* 2028 */ {10, 3}, + /* 2029 */ {9, 22}, + /* 2030 */ {9, 11} + }; + + lbRTC_ymd_t kyuu_ymd; + + // If the year is within the pre-calculated range, use the stored values + if ((year >= lbRk_HARVEST_MOON_YEAR_MIN) && (year < lbRk_HARVEST_MOON_YEAR_MAX + 1)) { + year -= lbRk_HARVEST_MOON_YEAR_MIN; + harvest_moon_day->month = ev_day[year].month; + harvest_moon_day->day = ev_day[year].day; + return; + } + + // If the year is outside the pre-calculated range, calculate the Harvest Moon Day + // using the Japanese lunisolar date of 8th month 15th day. + kyuu_ymd.year = year; + kyuu_ymd.month = 8; + kyuu_ymd.day = 15; + lbRk_ToSeiyouReki(harvest_moon_day, &kyuu_ymd); +} diff --git a/rel/lb_rtc.c b/rel/lb_rtc.c new file mode 100644 index 00000000..6631ac0e --- /dev/null +++ b/rel/lb_rtc.c @@ -0,0 +1,1078 @@ +/* + * lb_rtc.c + * + * This source file contains a set of functions for handling + * date and time calculations and conversions in the context of the lbRTC system. + * The lbRTC system is designed to manage timekeeping and manipulation of date-times. + * + * Functions in this file include (but are not limited to): + * + * - lbRTC_HardTime: Retrieve the hardware time. + * - lbRTC_IsAbnormal: Check if the lbRTC system is in an abnormal state. + * - lbRTC_Sampling: Perform a sampling operation on the lbRTC system. + * - lbRTC_SetTime: Set the current time in the lbRTC system. + * - lbRTC_GetTime: Get the current time from the lbRTC system. + * - lbRTC_GetDaysByMonth: Get the number of days in a given month and year. + * - lbRTC_IsEqualDate: Check if two dates are equal. + * - lbRTC_IsEqualTime: Check if two times are equal. + * - lbRTC_IsOverTime: Check if one time is greater than another time. + * - lbRTC_IsOverRTC: Check if the given time is greater than the current time. + * - lbRTC_IntervalTime: Calculate the interval between two times. + * - lbRTC_GetIntervalDays: Calculate the interval between two times in days. + * - lbRTC_GetIntervalDays2: Calculate the interval between two times (date only) in days. + * - lbRTC_Add_YY: Add years to a time. + * - lbRTC_Add_MM: Add months to a time. + * - lbRTC_Add_DD: Add days to a time. + * - lbRTC_Add_hh: Add hours to a time. + * - lbRTC_Add_mm: Add minutes to a time. + * - lbRTC_Add_ss: Add seconds to a time. + * - lbRTC_Add_Date: Add a time duration to a time. + * - lbRTC_Sub_YY: Subtract years from a time. + * - lbRTC_Sub_MM: Subtract months from a time. + * - lbRTC_Sub_DD: Subtract days from a time. + * - lbRTC_Sub_hh: Subtract hours from a time. + * - lbRTC_Sub_mm: Subtract minutes from a time. + * - lbRTC_Sub_ss: Subtract seconds from a time. + * - lbRTC_Week: Calculate the day of the week for a given date. + * - lbRTC_TimeCopy: Copy one time structure to another. + * - lbRTC_IsValidTime: Check if the given time is valid. + * - lbRTC_time_c_save_data_check: Check if the given time from save data is valid. + * - lbRTC_Weekly_day: Calculate the day of the week for a given date, considering weeks and the desired day of the week. + */ + +#include "lb_rtc.h" + +#include "m_common_data.h" +#include "m_lib.h" +#include "types.h" + +static BOOL l_lbRTC_isInitial = TRUE; +static lbRTC_time_c l_lbRTC_Time; +static BOOL l_lbRTC_IsSampled; + +/** + * @brief Get the current hardware time in ticks. + * + * This function retrieves the current hardware time using the OSGetTime() + * function and returns it as OSTime. + * + * @return OSTime representing the current hardware time in ticks. + */ +static OSTime lbRTC_GetHardTime() { + return OSGetTime(); +} + +/** + * @brief Wrapper function for lbRTC_GetHardTime(). + * + * This function is an external wrapper for the lbRTC_GetHardTime() function. + * It retrieves the current hardware time and returns it as OSTime. + * + * @return OSTime representing the current hardware time in ticks. + */ +extern OSTime lbRTC_HardTime() { + return lbRTC_GetHardTime(); +} + +/** + * @brief Convert OSCalendarTime to lbRTC_time_c structure. + * + * This function converts the given OSCalendarTime structure into + * an lbRTC_time_c structure by copying the relevant fields. + * + * @param calendar_time Pointer to the source OSCalendarTime structure. + * @param datetime Pointer to the destination lbRTC_time_c structure. + */ +static void lbRTC_CalenderTimeToRTCTime(const OSCalendarTime* calendar_time, lbRTC_time_c* datetime) { + datetime->sec = calendar_time->sec; + datetime->min = calendar_time->min; + datetime->hour = calendar_time->hour; + datetime->day = calendar_time->mday; + datetime->weekday = calendar_time->wday; + datetime->month = calendar_time->mon + 1; + datetime->year = calendar_time->year; +} + +/* @unused ? OSTime lbRTC_NowHardRtcTime() */ + +/* @unused ? OSTime lbRTC_SavedHardRtcTime() */ + +/** + * @brief Convert lbRTC_time_c to ticks. + * + * This function converts the given lbRTC_time_c structure to ticks (OSTime) + * using OSCalendarTimeToTicks() function. + * + * @param time Pointer to the lbRTC_time_c structure to be converted. + * @return OSTime representing the given lbRTC_time_c in ticks. + */ +static OSTime lbRTC_RTCTimeToTicks(const lbRTC_time_c* time) { + OSCalendarTime ctime; + int i; + int month = time->month; + + // Copy the lbRTC_time_c structure fields to OSCalendarTime fields + ctime.sec = time->sec; + ctime.min = time->min; + ctime.hour = time->hour; + ctime.mday = time->day; + ctime.mon = month - 1; + ctime.year = time->year; + ctime.wday = month; + ctime.yday = 0; + + // Get calendar year-day to start of current month + for (i = 1; i < month; i++) { + ctime.yday += lbRTC_GetDaysByMonth(time->year, (lbRTC_month_t)i); + } + + ctime.yday += time->day; + ctime.yday -= 1; + ctime.msec = 0; + ctime.usec = 0; + + // Convert the OSCalendarTime structure to ticks and return it + return OSCalendarTimeToTicks(&ctime); +} + +/** + * @brief Get the current game time as an lbRTC_time_c structure. + * + * This function calculates the current game time by getting the hardware time, + * adding the saved time delta, converting the result to an OSCalendarTime + * structure, and finally converting it to an lbRTC_time_c structure. + * + * @param time Pointer to the lbRTC_time_c structure that will hold the game time. + */ +static void lbRTC_GetGameTime(lbRTC_time_c* time) { + OSCalendarTime ctime; + OSTime t = lbRTC_GetHardTime(); + t += Save_Get(time_delta); + + OSTicksToCalendarTime(t, &ctime); + lbRTC_CalenderTimeToRTCTime(&ctime, time); +} + +/* @unused ? void lbRTC_GetSaveExpectGameTime(lbRTC_time_c* time) */ + +/** + * @brief Initialize lbRTC module. + * + * This function initializes the lbRTC module by setting l_lbRTC_isInitial + * to FALSE if it is TRUE. + * + * @return 0 after initialization. + */ +static int lbRTC_Initial() { + if (l_lbRTC_isInitial == TRUE) { + l_lbRTC_isInitial = FALSE; + } + + return 0; +} + +/** + * @brief Check if the lbRTC module is initialized and get the game time. + * + * This function initializes the lbRTC module if it is not already + * initialized, sets l_lbRTC_isInitial to TRUE, and gets the current game + * time using lbRTC_GetGameTime(). + * + * @param time Pointer to an lbRTC_time_c structure that will hold the game time. + * @return 0 after checking and getting the game time. + */ +static int lbRTC_IsOki(lbRTC_time_c* time) { + lbRTC_Initial(); + l_lbRTC_isInitial = TRUE; + lbRTC_GetGameTime(time); + return 0; +} + +/** + * @brief Check if the lbRTC module is in an abnormal state. + * + * This function checks if the lbRTC module is in an abnormal state by + * comparing the current game time with the previously sampled game time. + * It is assumed that a lot of debug code is missing in this function. + * + * @return 0 if the lbRTC module is in a normal state, non-zero otherwise. + */ +extern int lbRTC_IsAbnormal() { + /* Lots of debug code missing here */ + lbRTC_time_c time; + + int res = lbRTC_IsOki(&time); + if (res == 0 && l_lbRTC_IsSampled == TRUE) { + if (time.sec + time.min * lbRTC_SECONDS_PER_MINUTE != + l_lbRTC_Time.sec + l_lbRTC_Time.min * lbRTC_SECONDS_PER_MINUTE) { + return res; + } + } + + return res; +} + +/** + * @brief Sample the game time using lbRTC_IsOki(). + * + * This function samples the game time by checking if the lbRTC module + * is in a normal state using lbRTC_IsOki() and sets l_lbRTC_IsSampled to + * TRUE if it is not already TRUE. + */ +extern void lbRTC_Sampling() { + lbRTC_time_c time; + + if (lbRTC_IsOki(&time) == 0 && l_lbRTC_IsSampled == FALSE) { + l_lbRTC_Time.sec = time.sec; + l_lbRTC_Time.min = time.min; + /* Likely debug here */ + l_lbRTC_IsSampled = TRUE; + } +} + +/** + * @brief Set the game time. + * + * This function sets the game time based on the given lbRTC_time_c structure. + * If the RTC feature is enabled and not crashed, it updates the time delta. + * Otherwise, it copies the given time to the appropriate rtc_time location. + * + * @param time Pointer to the lbRTC_time_c structure containing the time to be set. + */ +extern void lbRTC_SetTime(lbRTC_time_c* time) { + if (Common_Get(time.rtc_enabled) == TRUE && !Common_Get(time.rtc_crashed)) { + Save_Set(time_delta, lbRTC_RTCTimeToTicks(time) - lbRTC_GetHardTime()); + } + else { + lbRTC_TimeCopy(Common_GetPointer(time.rtc_time), time); + } +} + +/** + * @brief Get the game time. + * + * This function gets the game time and stores it in the provided lbRTC_time_c + * structure. If the RTC feature is enabled and not crashed, it retrieves the + * game time using lbRTC_GetGameTime(). Otherwise, it copies the rtc_time + * to the provided structure. + * + * @param time Pointer to the lbRTC_time_c structure that will hold the game time. + */ +extern void lbRTC_GetTime(lbRTC_time_c* time) { + if (Common_Get(time.rtc_enabled) == TRUE && !Common_Get(time.rtc_crashed)) { + lbRTC_GetGameTime(time); + } + else { + lbRTC_TimeCopy(time, Common_GetPointer(time.rtc_time)); + } +} + +/** + * @brief Get the number of days in a given month for a specific year. + * + * This function returns the number of days in a given month for a specific + * year, taking into account leap years. + * + * @param year The year in which the month occurs. + * @param month The month for which the number of days is requested. + * @return lbRTC_day_t representing the number of days in the given month. + */ +extern lbRTC_day_t lbRTC_GetDaysByMonth(lbRTC_year_t year, lbRTC_month_t month) { + static const lbRTC_day_t days_month[2][lbRTC_MONTHS_MAX + 1] = { + // Regular year + { + 0, + 31, 28, 31, 30, 31, 30, + 31, 31, 30, 31, 30, 31 + }, + + // Leap year + { + 0, + 31, 29, 31, 30, 31, 30, + 31, 31, 30, 31, 30, 31 + } + }; + + int year_type = lbRTC_IS_LEAPYEAR(year) == TRUE ? 1 : 0; + return days_month[year_type][month]; +} + +typedef union { + int raw; + lbRTC_ymd_t ymd; +} ymd_u; + +/** + * @brief Compare two dates to determine if they are equal, lesser, or greater. + * + * This function compares two given dates (year, month, day) and returns the + * result as an RTC_EQUALITY enumeration value (lbRTC_EQUAL, lbRTC_LESS, + * or lbRTC_OVER). + * + * @param y0 Year of the first date. + * @param m0 Month of the first date. + * @param d0 Day of the first date. + * @param y1 Year of the second date. + * @param m1 Month of the second date. + * @param d1 Day of the second date. + * @return RTC_EQUALITY value representing the comparison result. + */ +extern int lbRTC_IsEqualDate( + lbRTC_year_t y0, lbRTC_month_t m0, lbRTC_day_t d0, + lbRTC_year_t y1, lbRTC_month_t m1, lbRTC_day_t d1 +) { + ymd_u ymd0, ymd1; + int res; + + ymd0.ymd.year = y0; + ymd0.ymd.month = m0; + ymd0.ymd.day = d0; + + ymd1.ymd.year = y1; + ymd1.ymd.month = m1; + ymd1.ymd.day = d1; + + res = ymd0.raw - ymd1.raw; + if (res == 0) { + return lbRTC_EQUAL; + } + + if (res < 0) { + return lbRTC_LESS; + } + return lbRTC_OVER; +} + +/** + * @brief Check if two lbRTC_time_c structures are equal based on specified flags. + * + * This function checks if two lbRTC_time_c structures are equal by comparing + * their time components according to the specified flags (seconds, minutes, hours, + * weekdays, days, months, years). + * + * @param t0 Pointer to the first lbRTC_time_c structure. + * @param t1 Pointer to the second lbRTC_time_c structure. + * @param flags Bitmask of lbRTC_check_t flags specifying which components to compare. + * @return TRUE if all specified components are equal, FALSE otherwise. + */ +extern int lbRTC_IsEqualTime(const lbRTC_time_c* t0, const lbRTC_time_c* t1, int flags) { + int equal = 0; + + if (flags & lbRTC_CHECK_SECONDS) { + if (t0->sec == t1->sec) { + equal |= lbRTC_CHECK_SECONDS; + } + } + + if (flags & lbRTC_CHECK_MINUTES) { + if (t0->min == t1->min) { + equal |= lbRTC_CHECK_MINUTES; + } + } + + if (flags & lbRTC_CHECK_HOURS) { + if (t0->hour == t1->hour) { + equal |= lbRTC_CHECK_HOURS; + } + } + + if (flags & lbRTC_CHECK_WEEKDAYS) { + if (t0->weekday == t1->weekday) { + equal |= lbRTC_CHECK_WEEKDAYS; + } + } + + if (flags & lbRTC_CHECK_DAYS) { + if (t0->day == t1->day) { + equal |= lbRTC_CHECK_DAYS; + } + } + + if (flags & lbRTC_CHECK_MONTHS) { + if (t0->month == t1->month) { + equal |= lbRTC_CHECK_MONTHS; + } + } + + if (flags & lbRTC_CHECK_YEARS) { + if (t0->year == t1->year) { + equal |= lbRTC_CHECK_YEARS; + } + } + + return (equal & flags) == flags; +} + +/** + * @brief Determine if a given time is greater (over) another time. + * + * This function compares two lbRTC_time_c structures to determine if the first + * time (t0) is greater (over) the second time (t1). Returns lbRTC_LESS if the first + * time is lesser than or equal to the second time, and lbRTC_OVER if the first time + * is greater than the second time. + * + * @param t0 Pointer to the first lbRTC_time_c structure. + * @param t1 Pointer to the second lbRTC_time_c structure. + * @return lbRTC_LESS if t0 is lesser than or equal to t1, lbRTC_OVER if t0 is greater than t1. + */ +extern int lbRTC_IsOverTime(const lbRTC_time_c* t0, const lbRTC_time_c* t1) { + if (t1->year < t0->year) { + return lbRTC_LESS; + } + + if (t1->year == t0->year) { + if (t1->month >= t0->month) { + if (t1->month == t0->month) { + if (t1->day >= t0->day) { + if (t1->day == t0->day) { + if (t1->hour >= t0->hour) { + if (t1->hour == t0->hour) { + if (t1->min >= t0->min) { + if (t1->min == t0->min) { + if (t1->sec < t0->sec) { + return lbRTC_LESS; + } + } + } else { + return lbRTC_LESS; + } + } + } else { + return lbRTC_LESS; + } + } + } else { + return lbRTC_LESS; + } + } + } else { + return lbRTC_LESS; + } + } + + return lbRTC_OVER; +} + +/** + * @fabricated + * + * @brief Check if the current RTC time is equal to the given time based on specified flags. + * + * This function compares the current RTC time with the given time and returns TRUE + * if the specified components are equal, based on the check_flags provided. + * + * @param time Pointer to the lbRTC_time_c structure representing the time to compare. + * @param check_flags Bitmask of lbRTC_check_t flags specifying which components to compare. + * @return TRUE if all specified components are equal, FALSE otherwise. + */ +/* +extern int lbRTC_IsJustAtRTC(const lbRTC_time_c* time, int check_flags) { + lbRTC_time_c rtc_time; + + lbRTC_GetTime(&rtc_time); + return lbRTC_IsEqualTime(time, &rtc_time, check_flags); +} +*/ + +/** + * @brief Check if the given time is greater (over) the current RTC time. + * + * This function compares the given time with the current RTC time and returns + * TRUE if the given time is greater (over) the current RTC time, and FALSE otherwise. + * + * @param time Pointer to the lbRTC_time_c structure representing the time to compare. + * @return TRUE if the given time is greater than the current RTC time, FALSE otherwise. + */ +extern int lbRTC_IsOverRTC(const lbRTC_time_c* time) { + lbRTC_time_c rtc_time; + + lbRTC_GetTime(&rtc_time); + return lbRTC_IsOverTime(time, &rtc_time) == lbRTC_OVER; +} + +/* @unused extern int lbRTC_IsOverWeekRTC(lbRTC_time_c* time) */ + +/** + * @brief Calculate the interval of time between two given lbRTC_time_c structures in minutes. + * + * This function calculates the interval of time between two given lbRTC_time_c + * structures. It returns the interval in minutes as an integer. If time0 is earlier than + * time1, the interval will be negative. + * + * @note This function is intended to be used internally by lbRTC_IntervalTime(). + * + * @param time0 Pointer to the first lbRTC_time_c structure. + * @param time1 Pointer to the second lbRTC_time_c structure. + * @return Number of minutes between the two given times, negative if time0 is earlier than time1. + */ +static int lbRTC_IntervalTime_sub(const lbRTC_time_c* time0, const lbRTC_time_c* time1) { + OSTime osTime1 = lbRTC_RTCTimeToTicks(time1); + OSTime osTime0 = lbRTC_RTCTimeToTicks(time0); + + return OSTicksToSeconds(osTime0 - osTime1) / lbRTC_SECONDS_PER_MINUTE; +} + +/** + * @brief Calculate the interval of time between two given lbRTC_time_c structures in minutes. + * + * This function calculates the interval of time between two given lbRTC_time_c + * structures. It returns the interval in minutes as an integer. If time0 is earlier than + * time1, the interval will be negative. + * + * @param time0 Pointer to the first lbRTC_time_c structure. + * @param time1 Pointer to the second lbRTC_time_c structure. + * @return Number of minutes between the two given times, negative if time0 is earlier than time1. + */ +extern int lbRTC_IntervalTime(const lbRTC_time_c* time0, const lbRTC_time_c* time1) { + return lbRTC_IntervalTime_sub(time0, time1); +} + +/** + * @brief Calculate the number of days between two dates. + * + * This function calculates the number of days between two given lbRTC_time_c + * structures. It returns the interval in days as an integer. + * + * @param t0 Pointer to the lbRTC_time_c structure representing the first date. + * @param t1 Pointer to the lbRTC_time_c structure representing the second date. + * @return Number of days between the two given dates. + */ +extern int lbRTC_GetIntervalDays(const lbRTC_time_c* t0, const lbRTC_time_c* t1) { + static const int total_days[2][lbRTC_MONTHS_MAX + 1] = { + // Standard year + { + 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 + }, + // Leap year (flawed leap year calculation) + { + 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366 + } + }; + + int year_leap_period = (t1->year - t0->year) / 4; /* Total 'leap years' (missing extra not divisible by 100, except when divisible by 400 rule) */ + int extra_years = (t1->year - t0->year) % 4; /* Non-leap year remainder */ + int less_leap = (t0->year % 4) == 0; /* Is the lesser year a leap year? */ + int over_leap = (t1->year % 4) == 0; /* Is the greater year a leap year? */ + int leap_add = ((4 - (t0->year % 4)) % 4) < extra_years; /* Add leap day when leap day occurs during 'extra years' */ + + int days; + + if (t0->year > t1->year) { + return 0; + } + else { + if (t0->year == t1->year) { + if (t0->month > t1->month) { + return 0; + } + else { + if (t0->month == t1->month) { + if (t0->day > t1->day) { + return 0; + } + else { + if (t0->day == t1->day) { + if (t0->hour > t1->hour) { + return 0; + } + else { + if (t0->hour == t1->hour) { + if (t0->min > t1->min) { + return 0; + } + } + } + } + } + } + } + } + } + + /*** + * Every four years (incorrect) has 365 * 3 + 366 * 1 days (1461). + * The remaining years will be 365 * years, plus 1 if leap year occurs in range. + ***/ + days = year_leap_period * 1461 + extra_years * 365 + leap_add; + + /* Add up through t1 day */ + days += t1->day - 1; + days += total_days[over_leap][t1->month - 1]; + + /* Remove through t0 day */ + days -= t0->day - 1; + days -= total_days[less_leap][t0->month - 1]; + + return days; +} + +/** + * @brief Calculate the number of days between two dates using lbRTC_ymd_t structures. + * + * This function calculates the number of days between two given lbRTC_ymd_t + * structures. It returns the interval in days as an integer. If ymd0 is greater than + * ymd1, the interval will be negative. + * + * @param ymd0 Pointer to the first lbRTC_ymd_t structure. + * @param ymd1 Pointer to the second lbRTC_ymd_t structure. + * @return Number of days between the two given dates, negative if ymd0 is greater than ymd1. + */ +extern int lbRTC_GetIntervalDays2(const lbRTC_ymd_t* ymd0, const lbRTC_ymd_t* ymd1) { + lbRTC_time_c t0, t1; + int equality; + + mTM_ymd_2_time(&t0, ymd0); + mTM_ymd_2_time(&t1, ymd1); + + equality = lbRTC_IsEqualDate(t0.year, t0.month, t0.day, t1.year, t1.month, t1.day); + if (equality > lbRTC_EQUAL) { + return lbRTC_GetIntervalDays(&t1, &t0) * -1; + } + if (equality < lbRTC_EQUAL) { + return lbRTC_GetIntervalDays(&t0, &t1); + } + + return 0; +} + +/** + * @brief Add a specified number of years to an lbRTC_time_c structure. + * + * @param time Pointer to an lbRTC_time_c structure. + * @param year Integer value representing the number of years to add. + */ +extern void lbRTC_Add_YY(lbRTC_time_c* time, int year) { + time->year += (lbRTC_year_t)year; +} + +/** + * @brief Add a specified number of months to an lbRTC_time_c structure. + * + * @param time Pointer to an lbRTC_time_c structure. + * @param month Integer value representing the number of months to add. + */ +extern void lbRTC_Add_MM(lbRTC_time_c* time, int month) { + int current_mo = time->month; + current_mo += month; + if (current_mo > lbRTC_DECEMBER) { + lbRTC_Add_YY(time, current_mo / lbRTC_MONTHS_MAX); + current_mo %= lbRTC_MONTHS_MAX; + } + + time->month = (lbRTC_month_t)current_mo; +} + +/** + * @brief Add a specified number of days to an lbRTC_time_c structure. + * + * @param time Pointer to an lbRTC_time_c structure. + * @param day Integer value representing the number of days to add. + */ +extern void lbRTC_Add_DD(lbRTC_time_c* time, int day) { + int month_days = lbRTC_GetDaysByMonth(time->year, (lbRTC_month_t)time->month); + int days = time->day; + days += day; + + /* This could lead to a bug if adding days rolls over more than one month. */ + if (days > month_days) { + days -= month_days; + lbRTC_Add_MM(time, 1); + } + + time->day = (lbRTC_day_t)days; +} + +/** + * @brief Add a specified number of hours to an lbRTC_time_c structure. + * + * @param time Pointer to an lbRTC_time_c structure. + * @param hour Integer value representing the number of hours to add. + */ +extern void lbRTC_Add_hh(lbRTC_time_c* time, int hour) { + int curr_hr = time->hour; + + curr_hr += hour; + if (curr_hr >= lbRTC_HOURS_PER_DAY) { + lbRTC_Add_DD(time, curr_hr / lbRTC_HOURS_PER_DAY); + curr_hr %= lbRTC_HOURS_PER_DAY; + } + + time->hour = (lbRTC_hour_t)curr_hr; +} + +/** + * @brief Add a specified number of minutes to an lbRTC_time_c structure. + * + * @param time Pointer to an lbRTC_time_c structure. + * @param min Integer value representing the number of minutes to add. + */ +extern void lbRTC_Add_mm(lbRTC_time_c* time, int min) { + int curr_min = time->min; + + curr_min += min; + if (curr_min >= lbRTC_MINUTES_PER_HOUR) { + lbRTC_Add_hh(time, curr_min / lbRTC_MINUTES_PER_HOUR); + curr_min %= lbRTC_MINUTES_PER_HOUR; + } + + time->min = (lbRTC_min_t)curr_min; +} + +/** + * @brief Add a specified number of seconds to an lbRTC_time_c structure. + * + * @param time Pointer to an lbRTC_time_c structure. + * @param sec Integer value representing the number of seconds to add. + */ +extern void lbRTC_Add_ss(lbRTC_time_c* time, int sec) { + int curr_sec = time->sec; + + curr_sec += sec; + if (curr_sec >= lbRTC_SECONDS_PER_MINUTE) { + lbRTC_Add_mm(time, curr_sec / lbRTC_SECONDS_PER_MINUTE); + curr_sec %= lbRTC_SECONDS_PER_MINUTE; + } + + time->sec = (lbRTC_sec_t)curr_sec; +} + +/** + * @brief Add the values of an lbRTC_time_c structure to another lbRTC_time_c structure. + * + * This function adds the values of one lbRTC_time_c structure (add_time) to another + * lbRTC_time_c structure (time). The values are added in the order of least to most + * significant time unit: seconds, minutes, hours, days, months, years. + * + * @param time Pointer to an lbRTC_time_c structure, which will be modified. + * @param add_time Pointer to an lbRTC_time_c structure containing the values to add. + */ +extern void lbRTC_Add_Date(lbRTC_time_c* time, const lbRTC_time_c* add_time) { + lbRTC_Add_ss(time, add_time->sec); + lbRTC_Add_mm(time, add_time->min); + lbRTC_Add_hh(time, add_time->hour); + lbRTC_Add_DD(time, add_time->day); + lbRTC_Add_MM(time, add_time->month); + lbRTC_Add_YY(time, add_time->year); +} + +/** + * @brief Subtract a specified number of years from an lbRTC_time_c structure. + * + * @param time Pointer to an lbRTC_time_c structure. + * @param year Integer value representing the number of years to subtract. + */ +extern void lbRTC_Sub_YY(lbRTC_time_c* time, int year) { + time->year -= (lbRTC_year_t)year; +} + +/** + * @brief Subtract a specified number of months from an lbRTC_time_c structure. + * + * @param time Pointer to an lbRTC_time_c structure. + * @param month Integer value representing the number of months to subtract. + */ +extern void lbRTC_Sub_MM(lbRTC_time_c* time, int month) { + int mo = time->month - month; + if (mo < lbRTC_JANUARY) { + int t_mo = mo; + int sub_year; + + if (mo == 0) { + mo = lbRTC_DECEMBER; + sub_year = 1; + } + else { + t_mo = ABS(mo); + sub_year = t_mo / lbRTC_MONTHS_MAX + 1; /* Years to subtract */ + mo = lbRTC_MONTHS_MAX - (t_mo % lbRTC_MONTHS_MAX); /* Final month we end on */ + } + + lbRTC_Sub_YY(time, sub_year); + } + + time->month = (lbRTC_month_t)mo; +} + +/** + * @brief Subtract a specified number of days from an lbRTC_time_c structure. + * + * @param time Pointer to an lbRTC_time_c structure. + * @param days Integer value representing the number of days to subtract. + */ +extern void lbRTC_Sub_DD(lbRTC_time_c* time, int days) { + int day = time->day; + int month_days; + + if (time->month == lbRTC_JANUARY) { + month_days = lbRTC_GetDaysByMonth(time->year, (lbRTC_month_t)lbRTC_DECEMBER); + } + else { + month_days = lbRTC_GetDaysByMonth(time->year, (lbRTC_month_t)(time->month - 1)); + } + + day -= days; + + /* Check if day rolled back to previous month */ + if (day <= 0) { + if (day == 0) { + day = month_days; /* Landed on last day of the month */ + } + else { + day += month_days; /* Bring day positive */ + } + + /* Another 'bug' here. If more than 1 month of days is subtracted, month will be wrong */ + lbRTC_Sub_MM(time, 1); + } + + time->day = (lbRTC_day_t)day; +} + +/** + * @brief Subtract a specified number of hours from an lbRTC_time_c structure. + * + * @param time Pointer to an lbRTC_time_c structure. + * @param hour Integer value representing the number of hours to subtract. + */ +extern void lbRTC_Sub_hh(lbRTC_time_c* time, int hour) { + int h = time->hour; + h -= hour; + + if (h < 0) { + int temp_h = h; + int sub_days; + + temp_h = ABS(h); + sub_days = temp_h / lbRTC_HOURS_PER_DAY + 1; + h = lbRTC_HOURS_PER_DAY - (temp_h % lbRTC_HOURS_PER_DAY); + + /* Check if we're rolling over the day */ + if (h == lbRTC_HOURS_PER_DAY) { + h = 0; + sub_days--; + } + + lbRTC_Sub_DD(time, sub_days); + } + + time->hour = (lbRTC_hour_t)h; +} + +/** + * @brief Subtract a specified number of minutes from an lbRTC_time_c structure. + * + * @param time Pointer to an lbRTC_time_c structure. + * @param min Integer value representing the number of minutes to subtract. + */ +extern void lbRTC_Sub_mm(lbRTC_time_c* time, int min) { + int t_min = time->min; + t_min -= min; + + if (t_min < 0) { + int temp_min = t_min; + int sub_hours; + + temp_min = ABS(t_min); + sub_hours = temp_min / lbRTC_MINUTES_PER_HOUR + 1; + t_min = lbRTC_MINUTES_PER_HOUR - (temp_min % lbRTC_MINUTES_PER_HOUR); + + /* Check if we're rolling over the hour */ + if (t_min == lbRTC_MINUTES_PER_HOUR) { + t_min = 0; + sub_hours--; + } + + lbRTC_Sub_hh(time, sub_hours); + } + + time->min = (lbRTC_min_t)t_min; +} + +/** + * @brief Subtract a specified number of seconds from an lbRTC_time_c structure. + * + * @param time Pointer to an lbRTC_time_c structure. + * @param sec Integer value representing the number of seconds to subtract. + */ +extern void lbRTC_Sub_ss(lbRTC_time_c* time, int sec) { + int t_sec = time->sec; + t_sec -= sec; + + if (t_sec < 0) { + int temp_sec = t_sec; + int sub_mins; + + temp_sec = ABS(t_sec); + sub_mins = temp_sec / lbRTC_SECONDS_PER_MINUTE + 1; + t_sec = lbRTC_SECONDS_PER_MINUTE - (temp_sec % lbRTC_SECONDS_PER_MINUTE); + + /* Check if we're rolling over the minute */ + if (t_sec == lbRTC_SECONDS_PER_MINUTE) { + t_sec = 0; + sub_mins--; + } + + lbRTC_Sub_mm(time, sub_mins); + } + + time->sec = (lbRTC_sec_t)t_sec; +} + +/** + * @fabricated + * + * @brief Subtract the values of an lbRTC_time_c structure from another lbRTC_time_c structure. + * + * This function subtracts the values of one lbRTC_time_c structure (sub_time) from another + * lbRTC_time_c structure (time). The values are subtracted in the order of least to most + * significant time unit: seconds, minutes, hours, days, months, years. + * + * @param time Pointer to an lbRTC_time_c structure, which will be modified. + * @param sub_time Pointer to an lbRTC_time_c structure containing the values to subtract. + */ +/* +extern void lbRTC_Sub_Date(lbRTC_time_c* time, const lbRTC_time_c* sub_time) { + lbRTC_Sub_ss(time, sub_time->sec); + lbRTC_Sub_mm(time, sub_time->min); + lbRTC_Sub_hh(time, sub_time->hour); + lbRTC_Sub_DD(time, sub_time->day); + lbRTC_Sub_MM(time, sub_time->month); + lbRTC_Sub_YY(time, sub_time->year); +} +*/ + +/** + * @brief Calculate the day of the week for a given date. + * + * @param year Year value. + * @param month Month value. + * @param day Day value. + * @return The day of the week as an lbRTC_weekday_t value. + */ +extern lbRTC_weekday_t lbRTC_Week(lbRTC_year_t year, lbRTC_month_t month, lbRTC_day_t day) { + /* 00:00:00 @ January 1st, 1901 */ + static const lbRTC_time_c a_time = { + 0, 0, 0, + 1, 0, 1, + 1901 + }; + + /* This initialization is required lmao */ + lbRTC_time_c b_time = { + 0, 0, 0, + 0, 0, 0, + 0000 + }; + + b_time.year = year; + b_time.month = month; + b_time.day = day; + + return (lbRTC_weekday_t)((lbRTC_GetIntervalDays(&a_time, &b_time) + 2) % lbRTC_WEEK); +} + +/** + * @brief Copy the values of one lbRTC_time_c structure to another. + * + * @param dst Pointer to the destination lbRTC_time_c structure. + * @param src Pointer to the source lbRTC_time_c structure. + */ +extern void lbRTC_TimeCopy(lbRTC_time_c* dst, const lbRTC_time_c* src) { + *dst = *src; +} + +/** + * @brief Check if the given time is valid. + * + * @param time Pointer to an lbRTC_time_c structure. + * @return Non-zero (TRUE) if the time is valid, zero (FALSE) otherwise. + */ +extern int lbRTC_IsValidTime(const lbRTC_time_c* time) { + static const u8 day_tbl[] = { + 00, + 31, 28, 31, 30, 31, 30, + 31, 31, 30, 31, 30, 31 + }; + + int res; + + if ( + (time->year < lbRTC_YEAR_MIN || time->year > lbRTC_YEAR_MAX) || + (time->month == lbRTC_MONTHS_BEGIN || time->month > lbRTC_MONTHS_MAX) || + (time->day == 0) || + (time->hour > 23) || + (time->min > 59) || + (time->sec > 59) + ) { + res = FALSE; + } + else { + if (time->day == 29 && time->month == 2) { + res = lbRTC_IS_LEAPYEAR(time->year); + } + else if (time->day > day_tbl[time->month]) { + res = FALSE; + } + else { + res = TRUE; + } + } + + return res; +} + +/** + * @brief Check if the given time is valid for save data. + * + * @param time Pointer to an lbRTC_time_c structure. + * @return Non-zero (TRUE) if the time is invalid, zero (FALSE) otherwise. + */ +extern int lbRTC_time_c_save_data_check(const lbRTC_time_c* time) { + int res = FALSE; + + if ( + (time->sec < 60) && + (time->min < 60) && + (time->hour < 24) && + (time->day >= 1 && time->day <= 31) && + (time->weekday < 7) && + (time->month >= 1 && time->month <= 12) && + (time->year >= GAME_YEAR_MIN + 1 && time->year <= GAME_YEAR_MAX - 1) + ) { + res = TRUE; + } + + return !res; +} + +/** + * @brief Calculate the day of the month for a given week and weekday. + * + * @param year Year value. + * @param month Month value. + * @param weeks Integer representing the week number in the month. + * @param weekday Integer representing the day of the week. + * @return The day of the month as an integer. + */ +extern int lbRTC_Weekly_day(lbRTC_year_t year, lbRTC_month_t month, int weeks, int weekday) { + int t_weekday; + + int month_first_weekday = lbRTC_Week(year, month, 1); + lbRTC_day_t month_days = lbRTC_GetDaysByMonth(year, month); + + weeks--; + t_weekday = (int)(((weekday - month_first_weekday) + lbRTC_WEEK) % lbRTC_WEEK) + 1; + + while (weeks > 0) { + t_weekday += 7; + if (t_weekday > month_days) { + t_weekday -= 7; + break; + } + + weeks--; + } + + return t_weekday; +}