/** * @file * @brief computes whether day is during daylight savings time * @note Public domain algorithms from ACM * @author Steve Karg * @date 1997 * @copyright SPDX-License-Identifier: CC-PDDC */ #include #include #include "days.h" #include "dst.h" /** * This function returns the number of seconds after midnight * * @param hours - hours after midnight (0..23) * @param minutes - minutes after hour (0..59) * @param seconds - holds seconds after minute (0..59) * * @return true if date-time falls in DST. false if not. */ static uint32_t time_to_seconds(uint32_t hours, uint32_t minutes, uint32_t seconds) { return (((hours) * 60 * 60) + ((minutes) * 60) + (seconds)); } /** * This function returns the day of the month for starting the Nth week * * @param year - Year of our Lord A.D. (1900..9999) * @param month - months of the year (1=Jan,...,12=Dec) * @param ordinal - Ordinal Day of the Month * 1=1st, 2=2nd, 3=3rd, 4=4th, or 5=LAST * @return day of the month (1..31) */ static uint8_t ordinal_week_month_day(uint16_t year, uint8_t month, uint8_t ordinal) { uint8_t day = 0; if (ordinal == 5) { /* last week of the month */ day = days_per_month(year, month) - 6; } else { if (ordinal) { ordinal--; day = 1 + (ordinal * 7); } } return day; } /** * This function returns true if the date-time is during DST * * @param year - Year of our Lord A.D. (2000,2001,..2099) * @param month - months of the year (1=Jan,...,12=Dec) * @param day - day of the month (1..31) * @param hour - hours after midnight (0..23) * @param minute - minutes after hour (0..59) * @param second - holds seconds after minute (0..59) * * @return true if date-time falls in DST. false if not. */ bool dst_active( struct daylight_savings_data *data, uint16_t year, uint8_t month, uint8_t day, uint8_t hour, uint8_t minute, uint8_t second) { bool active = false; uint8_t i = 0; uint32_t time_now = 0; uint32_t time_dst = 0; uint8_t day_of_week = 0; uint8_t days = 0; uint32_t days_begin = 0; uint32_t days_now = 0; uint32_t days_end = 0; if (data->Ordinal) { if ((month >= data->Begin_Month) && (month <= data->End_Month)) { if (month == data->Begin_Month) { days = days_per_month(year, month); i = ordinal_week_month_day(year, month, data->Begin_Week); for (; i <= days; i++) { day_of_week = days_of_week( data->Epoch_Day, days_since_epoch(data->Epoch_Year, year, month, i)); if (day_of_week == data->Begin_Day) { if (day == i) { time_now = time_to_seconds(hour, minute, second); /* begins at 2 AM Standard Time */ time_dst = time_to_seconds(2, 0, 0); if (time_now >= time_dst) { active = true; } } else if (day > i) { active = true; } break; } } } else if (month == data->End_Month) { days = days_per_month(year, month); i = ordinal_week_month_day(year, month, data->End_Week); for (; i <= days; i++) { day_of_week = days_of_week( data->Epoch_Day, days_since_epoch(data->Epoch_Year, year, month, i)); if (day_of_week == data->End_Day) { if (day == i) { time_now = time_to_seconds(hour, minute, second); /* ends at 2 AM Daylight time, which is 1 AM Standard Time */ time_dst = time_to_seconds(1, 0, 0); if (time_now < time_dst) { active = true; } } else if (day < i) { active = true; } break; } } } else { /* months between the beginning and end months */ active = true; } } } else { days_now = days_since_epoch(data->Epoch_Year, year, month, day); days_begin = days_since_epoch( data->Epoch_Year, year, data->Begin_Month, data->Begin_Day); days_end = days_since_epoch( data->Epoch_Year, year, data->End_Month, data->End_Day); if ((days_now >= days_begin) && (days_now <= days_end)) { if (days_now == days_begin) { time_now = time_to_seconds(hour, minute, second); /* begins at 2 AM Standard Time */ time_dst = time_to_seconds(2, 0, 0); if (time_now >= time_dst) { active = true; } } else if (days_now == days_end) { time_now = time_to_seconds(hour, minute, second); /* ends at 2 AM Daylight time, which is 1 AM Standard Time */ time_dst = time_to_seconds(1, 0, 0); if (time_now < time_dst) { active = true; } } else { active = true; } } } return active; } /** * @brief This function sets the daylight savings time parameters * @param data - daylight savings time data * @param ordinal - true when ordinal day of month used, false if specific dates * @param begin_month - month DST begins 1=Jan,...,12=Dec * @param begin_day - day of the month DST begins * 1..31 for specific day, or day of week 1..7 for ordinal day * @param begin_which_day - which ordinal day of the month is used * 1=1st, 2=2nd, 3=3rd, 4=4th, or 5=LAST * @param end_month - month DST ends 1=Jan,...,12=Dec * @param end_day - day of the month DST ends * 1..31 for specific day, or day of week 1..7 for ordinal day * @param end_which_day - which ordinal day of the month is used * 1=1st, 2=2nd, 3=3rd, 4=4th, or 5=LAST * @param epoch_day - day of the week for the BACnet Epoch (1=Monday..7=Sunday) * @param epoch_year - year of the BACnet Epoch (1900..9999) */ void dst_init( struct daylight_savings_data *data, bool ordinal, uint8_t begin_month, uint8_t begin_day, uint8_t begin_which_day, uint8_t end_month, uint8_t end_day, uint8_t end_which_day, uint8_t epoch_day, uint16_t epoch_year) { if (data) { data->Ordinal = ordinal; data->Begin_Month = begin_month; data->Begin_Day = begin_day; data->Begin_Week = begin_which_day; data->End_Month = end_month; data->End_Day = end_day; data->End_Week = end_which_day; data->Epoch_Day = epoch_day; data->Epoch_Year = epoch_year; } } /** * Initializes the daylight savings time parameters to their defaults. */ void dst_init_defaults(struct daylight_savings_data *data) { if (data) { /* Starts: Second=2 Sunday=7 in March=3 */ /* Ends: First=1 Sunday=7 in November=11 */ data->Ordinal = true; data->Begin_Month = 3; data->Begin_Day = 7; data->Begin_Week = 2; data->End_Month = 11; data->End_Day = 7; data->End_Week = 1; /* BACnet Epoch */ data->Epoch_Day = 1 /* Monday=1 */; data->Epoch_Year = 1900; } }