From fd3be47d86b683e28861b30be611055eaf115f1e Mon Sep 17 00:00:00 2001 From: Steve Karg Date: Sun, 24 Nov 2024 11:20:25 -0600 Subject: [PATCH] Feature/date time mstimer clock (#861) * Added daylight savings time calculation module with unit testing. * Added datetime daylight savings time and clock API * Added basic datetime_local() clock using mstimer as basis and time-sync option. Integrated clock with ports/stm32f4xx example. --- CMakeLists.txt | 2 + ports/stm32f4xx/CMakeLists.txt | 5 + ports/stm32f4xx/Makefile | 15 +- ports/stm32f4xx/bacnet.c | 8 +- ports/stm32f4xx/device.c | 84 ++++++- src/bacnet/basic/sys/datetime_mstimer.c | 262 +++++++++++++++++++++ src/bacnet/basic/sys/days.c | 24 +- src/bacnet/basic/sys/days.h | 3 + src/bacnet/basic/sys/dst.c | 229 ++++++++++++++++++ src/bacnet/basic/sys/dst.h | 58 +++++ src/bacnet/datalink/dlmstp.c | 1 - src/bacnet/datetime.c | 21 +- src/bacnet/datetime.h | 33 ++- test/CMakeLists.txt | 1 + test/bacnet/basic/sys/color_rgb/src/main.c | 10 +- test/bacnet/basic/sys/days/src/main.c | 112 ++++++++- test/bacnet/basic/sys/dst/CMakeLists.txt | 42 ++++ test/bacnet/basic/sys/dst/src/main.c | 137 +++++++++++ test/bacnet/datetime/src/main.c | 38 +-- 19 files changed, 992 insertions(+), 93 deletions(-) create mode 100644 src/bacnet/basic/sys/datetime_mstimer.c create mode 100644 src/bacnet/basic/sys/dst.c create mode 100644 src/bacnet/basic/sys/dst.h create mode 100644 test/bacnet/basic/sys/dst/CMakeLists.txt create mode 100644 test/bacnet/basic/sys/dst/src/main.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 0914f6c4..2e9130e0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -527,6 +527,8 @@ add_library(${PROJECT_NAME} src/bacnet/basic/sys/days.h src/bacnet/basic/sys/debug.c src/bacnet/basic/sys/debug.h + src/bacnet/basic/sys/dst.c + src/bacnet/basic/sys/dst.h src/bacnet/basic/sys/fifo.c src/bacnet/basic/sys/fifo.h src/bacnet/basic/sys/filename.c diff --git a/ports/stm32f4xx/CMakeLists.txt b/ports/stm32f4xx/CMakeLists.txt index f46a49df..3e5aca44 100644 --- a/ports/stm32f4xx/CMakeLists.txt +++ b/ports/stm32f4xx/CMakeLists.txt @@ -170,6 +170,7 @@ set(BACNET_PROJECT_SOURCE ${LIBRARY_BACNET_BASIC}/service/h_rd.c ${LIBRARY_BACNET_BASIC}/service/h_rp.c ${LIBRARY_BACNET_BASIC}/service/h_rpm.c + ${LIBRARY_BACNET_BASIC}/service/h_ts.c ${LIBRARY_BACNET_BASIC}/service/h_whohas.c ${LIBRARY_BACNET_BASIC}/service/h_whois.c ${LIBRARY_BACNET_BASIC}/service/h_wp.c @@ -178,6 +179,9 @@ set(BACNET_PROJECT_SOURCE ${LIBRARY_BACNET_BASIC}/service/s_ihave.c ${LIBRARY_BACNET_BASIC}/tsm/tsm.c ${LIBRARY_BACNET_BASIC}/sys/debug.c + ${LIBRARY_BACNET_BASIC}/sys/datetime_mstimer.c + ${LIBRARY_BACNET_BASIC}/sys/days.c + ${LIBRARY_BACNET_BASIC}/sys/dst.c ${LIBRARY_BACNET_BASIC}/sys/ringbuf.c ${LIBRARY_BACNET_BASIC}/sys/fifo.c ${LIBRARY_BACNET_BASIC}/sys/keylist.c @@ -214,6 +218,7 @@ set(BACNET_PROJECT_SOURCE ${LIBRARY_BACNET_CORE}/rp.c ${LIBRARY_BACNET_CORE}/rpm.c ${LIBRARY_BACNET_CORE}/timestamp.c + ${LIBRARY_BACNET_CORE}/timesync.c ${LIBRARY_BACNET_CORE}/weeklyschedule.c ${LIBRARY_BACNET_CORE}/dailyschedule.c ${LIBRARY_BACNET_CORE}/calendar_entry.c diff --git a/ports/stm32f4xx/Makefile b/ports/stm32f4xx/Makefile index c125bf3d..df81b18e 100644 --- a/ports/stm32f4xx/Makefile +++ b/ports/stm32f4xx/Makefile @@ -3,15 +3,15 @@ #+-------------------------------------------------------------------------------------------------+ TARGET=bacnet -BACNET_DIR = ../.. +BACNET_DIR = $(realpath ../..) BACNET_SRC := $(BACNET_DIR)/src BACNET_CORE := $(BACNET_SRC)/bacnet BACNET_BASIC := $(BACNET_CORE)/basic BACNET_INCLUDE := $(BACNET_SRC) -PLATFORM_DIR = . -LIBRARY_STM32 = ./external/STM32F4xx_StdPeriph_Driver/src -LIBRARY_STM32_INCLUDES = ./external/STM32F4xx_StdPeriph_Driver/inc -LIBRARY_CMSIS = ./external/CMSIS +PLATFORM_DIR = $(realpath .) +LIBRARY_STM32 = $(realpath ./external/STM32F4xx_StdPeriph_Driver/src) +LIBRARY_STM32_INCLUDES = $(realpath ./external/STM32F4xx_StdPeriph_Driver/inc) +LIBRARY_CMSIS = $(realpath ./external/CMSIS) CSTACK_TOOL := $(BACNET_DIR)/tools/avstack/avstack.pl MEMAP_TOOL := $(BACNET_DIR)/tools/memap/memap.py @@ -48,6 +48,7 @@ BASIC_SRC = \ $(BACNET_BASIC)/service/h_rd.c \ $(BACNET_BASIC)/service/h_rp.c \ $(BACNET_BASIC)/service/h_rpm.c \ + $(BACNET_BASIC)/service/h_ts.c \ $(BACNET_BASIC)/service/h_whohas.c \ $(BACNET_BASIC)/service/h_whois.c \ $(BACNET_BASIC)/service/h_wp.c \ @@ -55,6 +56,9 @@ BASIC_SRC = \ $(BACNET_BASIC)/service/s_iam.c \ $(BACNET_BASIC)/service/s_ihave.c \ $(BACNET_BASIC)/sys/debug.c \ + $(BACNET_BASIC)/sys/datetime_mstimer.c \ + $(BACNET_BASIC)/sys/days.c \ + $(BACNET_BASIC)/sys/dst.c \ $(BACNET_BASIC)/sys/ringbuf.c \ $(BACNET_BASIC)/sys/fifo.c \ $(BACNET_BASIC)/sys/keylist.c \ @@ -92,6 +96,7 @@ BACNET_SRC = \ $(BACNET_CORE)/rpm.c \ $(BACNET_CORE)/special_event.c \ $(BACNET_CORE)/timestamp.c \ + $(BACNET_CORE)/timesync.c \ $(BACNET_CORE)/weeklyschedule.c \ $(BACNET_CORE)/whohas.c \ $(BACNET_CORE)/whois.c \ diff --git a/ports/stm32f4xx/bacnet.c b/ports/stm32f4xx/bacnet.c index e803320f..4196b13a 100644 --- a/ports/stm32f4xx/bacnet.c +++ b/ports/stm32f4xx/bacnet.c @@ -14,8 +14,8 @@ #include "bacnet/datalink/datalink.h" #include "bacnet/npdu.h" #include "bacnet/basic/services.h" -#include "bacnet/basic/services.h" #include "bacnet/basic/tsm/tsm.h" +#include "bacnet/datetime.h" #include "bacnet/dcc.h" #include "bacnet/iam.h" /* BACnet objects */ @@ -116,6 +116,12 @@ void bacnet_init(void) SERVICE_CONFIRMED_REINITIALIZE_DEVICE, handler_reinitialize_device); apdu_set_confirmed_handler( SERVICE_CONFIRMED_WRITE_PROPERTY, handler_write_property); + /* local time and date */ + apdu_set_unconfirmed_handler( + SERVICE_UNCONFIRMED_TIME_SYNCHRONIZATION, + handler_timesync); + handler_timesync_set_callback_set(datetime_timesync); + datetime_init(); /* handle communication so we can shutup when asked */ apdu_set_confirmed_handler(SERVICE_CONFIRMED_DEVICE_COMMUNICATION_CONTROL, handler_device_communication_control); diff --git a/ports/stm32f4xx/device.c b/ports/stm32f4xx/device.c index 0f78a0e8..0ccfff10 100644 --- a/ports/stm32f4xx/device.c +++ b/ports/stm32f4xx/device.c @@ -18,6 +18,7 @@ #include "bacnet/bacstr.h" #include "bacnet/bacenum.h" #include "bacnet/apdu.h" +#include "bacnet/datetime.h" #include "bacnet/dcc.h" #include "bacnet/datalink/datalink.h" #include "bacnet/version.h" @@ -49,11 +50,12 @@ static struct my_object_functions { read_property_function Object_Read_Property; write_property_function Object_Write_Property; rpm_property_lists_function Object_RPM_List; -} Object_Table[] = { { OBJECT_DEVICE, NULL, /* don't init - recursive! */ - Device_Count, Device_Index_To_Instance, - Device_Valid_Object_Instance_Number, - Device_Object_Name, Device_Read_Property_Local, - Device_Write_Property_Local, Device_Property_Lists }, +} Object_Table[] = { { OBJECT_DEVICE, NULL, + /* don't init - recursive! */ + Device_Count, Device_Index_To_Instance, + Device_Valid_Object_Instance_Number, + Device_Object_Name, Device_Read_Property_Local, + Device_Write_Property_Local, Device_Property_Lists }, { OBJECT_ANALOG_INPUT, Analog_Input_Init, Analog_Input_Count, Analog_Input_Index_To_Instance, Analog_Input_Valid_Instance, Analog_Input_Object_Name, Analog_Input_Read_Property, @@ -116,7 +118,9 @@ static const char *BACnet_Version = BACNET_VERSION_TEXT; static uint8_t Device_UUID[16]; /* These three arrays are used by the ReadPropertyMultiple handler */ -static const int Device_Properties_Required[] = { PROP_OBJECT_IDENTIFIER, +static const int Device_Properties_Required[] = { + /* required properties for this object */ + PROP_OBJECT_IDENTIFIER, PROP_OBJECT_NAME, PROP_OBJECT_TYPE, PROP_SYSTEM_STATUS, PROP_VENDOR_NAME, PROP_VENDOR_IDENTIFIER, PROP_MODEL_NAME, PROP_FIRMWARE_REVISION, PROP_APPLICATION_SOFTWARE_VERSION, PROP_PROTOCOL_VERSION, @@ -126,8 +130,17 @@ static const int Device_Properties_Required[] = { PROP_OBJECT_IDENTIFIER, PROP_APDU_TIMEOUT, PROP_NUMBER_OF_APDU_RETRIES, PROP_DEVICE_ADDRESS_BINDING, PROP_DATABASE_REVISION, -1 }; -static const int Device_Properties_Optional[] = { PROP_DESCRIPTION, - PROP_LOCATION, PROP_MAX_MASTER, PROP_MAX_INFO_FRAMES, PROP_DEVICE_UUID, +static const int Device_Properties_Optional[] = { + /* optional properties for this object */ + PROP_DESCRIPTION, + PROP_LOCATION, + PROP_MAX_MASTER, + PROP_MAX_INFO_FRAMES, + PROP_DEVICE_UUID, + PROP_LOCAL_DATE, + PROP_LOCAL_TIME, + PROP_UTC_OFFSET, + PROP_DAYLIGHT_SAVINGS_STATUS, -1 }; static const int Device_Properties_Proprietary[] = { -1 }; @@ -686,6 +699,10 @@ int Device_Read_Property_Local(BACNET_READ_PROPERTY_DATA *rpdata) BACNET_BIT_STRING bit_string = { 0 }; BACNET_CHARACTER_STRING char_string = { 0 }; BACNET_OCTET_STRING octet_string = { 0 }; + BACNET_DATE bdate; + BACNET_TIME btime; + int16_t utc_offset_minutes; + bool dst_active; uint32_t i = 0; uint32_t count = 0; uint8_t *apdu = NULL; @@ -817,6 +834,23 @@ int Device_Read_Property_Local(BACNET_READ_PROPERTY_DATA *rpdata) octetstring_init(&octet_string, Device_UUID, sizeof(Device_UUID)); apdu_len = encode_application_octet_string(&apdu[0], &octet_string); break; + case PROP_LOCAL_TIME: + datetime_local(&bdate, &btime, &utc_offset_minutes, &dst_active); + apdu_len = encode_application_time(&apdu[0], &btime); + break; + case PROP_LOCAL_DATE: + datetime_local(&bdate, &btime, &utc_offset_minutes, &dst_active); + apdu_len = encode_application_date(&apdu[0], &bdate); + break; + case PROP_UTC_OFFSET: + datetime_local(&bdate, &btime, &utc_offset_minutes, &dst_active); + apdu_len = encode_application_signed(&apdu[0], utc_offset_minutes); + break; + case PROP_DAYLIGHT_SAVINGS_STATUS: + datetime_local(&bdate, &btime, &utc_offset_minutes, &dst_active); + apdu_len = + encode_application_boolean(&apdu[0], dst_active); + break; default: rpdata->error_class = ERROR_CLASS_PROPERTY; rpdata->error_code = ERROR_CODE_UNKNOWN_PROPERTY; @@ -1060,6 +1094,39 @@ bool Device_Write_Property_Local(BACNET_WRITE_PROPERTY_DATA *wp_data) wp_data->error_code = ERROR_CODE_INVALID_DATA_TYPE; } break; + case PROP_LOCAL_TIME: + status = write_property_type_valid( + wp_data, &value, BACNET_APPLICATION_TAG_TIME); + if (status) { + status = datetime_time_is_valid(&value.type.Time); + if (status) { + datetime_timesync(NULL, &value.type.Time, false); + } else { + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + } + } + break; + case PROP_LOCAL_DATE: + status = write_property_type_valid( + wp_data, &value, BACNET_APPLICATION_TAG_DATE); + if (status) { + status = datetime_date_is_valid(&value.type.Date); + if (status) { + datetime_timesync(&value.type.Date, NULL, false); + } else { + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + } + } + break; + case PROP_UTC_OFFSET: + status = write_property_type_valid( + wp_data, &value, BACNET_APPLICATION_TAG_SIGNED_INT); + if (status) { + datetime_utc_offset_minutes_set(value.type.Signed_Int); + } + break; case PROP_OBJECT_TYPE: case PROP_VENDOR_NAME: case PROP_VENDOR_IDENTIFIER: @@ -1076,6 +1143,7 @@ bool Device_Write_Property_Local(BACNET_WRITE_PROPERTY_DATA *wp_data) case PROP_DEVICE_ADDRESS_BINDING: case PROP_ACTIVE_COV_SUBSCRIPTIONS: case PROP_DATABASE_REVISION: + case PROP_DAYLIGHT_SAVINGS_STATUS: wp_data->error_class = ERROR_CLASS_PROPERTY; wp_data->error_code = ERROR_CODE_WRITE_ACCESS_DENIED; break; diff --git a/src/bacnet/basic/sys/datetime_mstimer.c b/src/bacnet/basic/sys/datetime_mstimer.c new file mode 100644 index 00000000..73ecbbfd --- /dev/null +++ b/src/bacnet/basic/sys/datetime_mstimer.c @@ -0,0 +1,262 @@ +/** + * @file + * @brief API for Milliseconds Timer based Time-of-Day Clock + * @author Steve Karg + * @date 2024 + * @copyright SPDX-License-Identifier: GPL-2.0-or-later WITH GCC-exception-2.0 + */ +#include +#include +#include +#include +#include +/* BACnet Stack defines - first */ +#include "bacnet/bacdef.h" +/* BACnet Stack API */ +#include "bacnet/basic/sys/dst.h" +#include "bacnet/basic/sys/mstimer.h" +#include "bacnet/datetime.h" + +/* local time */ +static BACNET_DATE_TIME BACnet_Date_Time; +static int16_t UTC_Offset_Minutes; +/* starting and stopping dates/times to determine DST */ +static struct daylight_savings_data DST_Range; +static bool DST_Enabled; +/* local time based on mstimer */ +static struct mstimer Date_Timer; + +/** + * @brief Synchronize the local time from the millisecond timer + */ +static void datetime_sync(void) +{ + bacnet_time_t seconds, elapsed_seconds; + unsigned long milliseconds; + + milliseconds = mstimer_elapsed(&Date_Timer); + elapsed_seconds = milliseconds / 1000UL; + if (elapsed_seconds) { + mstimer_restart(&Date_Timer); + seconds = datetime_seconds_since_epoch(&BACnet_Date_Time); + seconds += elapsed_seconds; + datetime_since_epoch_seconds(&BACnet_Date_Time, seconds); + /* generate a hundredths value */ + milliseconds -= (elapsed_seconds * 1000UL); + BACnet_Date_Time.time.hundredths = milliseconds / 10; + } +} + +/** + * @brief Get the local determination of daylight savings time being active + * @return true if DST is active, false otherwise + */ +static bool datetime_dst_active( + struct daylight_savings_data *dst, + BACNET_DATE_TIME *bdatetime, + bool enabled) +{ + bool active = false; + + if (enabled) { + active = dst_active( + dst, bdatetime->date.year, bdatetime->date.month, + bdatetime->date.day, bdatetime->time.hour, bdatetime->time.min, + bdatetime->time.sec); + } + + return active; +} + +/** + * @brief Get the local date and time + * @param bdate [out] The date to get + * @param btime [out] The time to get + * @param utc_offset_minutes [out] The UTC offset in minutes + * @param dst_active [out] The DST flag + * @return true if successful, false on error + */ +bool datetime_local( + BACNET_DATE *bdate, + BACNET_TIME *btime, + int16_t *utc_offset_minutes, + bool *dst_active) +{ + datetime_sync(); + if (bdate) { + datetime_copy_date(bdate, &BACnet_Date_Time.date); + } + if (btime) { + datetime_copy_time(btime, &BACnet_Date_Time.time); + } + if (utc_offset_minutes) { + *utc_offset_minutes = UTC_Offset_Minutes; + } + if (dst_active) { + *dst_active = + datetime_dst_active(&DST_Range, &BACnet_Date_Time, DST_Enabled); + } + + return true; +} + +/** + * @brief Get the UTC offset in minutes + * @return The UTC offset in minutes + */ +int16_t datetime_utc_offset_minutes(void) +{ + return UTC_Offset_Minutes; +} + +/** + * @brief Set the UTC offset in minutes + * @param minutes [in] The UTC offset in minutes + * @return true if successful, false on error + */ +bool datetime_utc_offset_minutes_set(int16_t minutes) +{ + UTC_Offset_Minutes = minutes; + + return true; +} + +/** + * @brief Get the Daylight Savings Enabled flag + * @return Daylight Savings Enabled flag + */ +bool datetime_dst_enabled(void) +{ + return DST_Enabled; +} + +/** + * @brief Set the Daylight Savings Enabled flag + * @param flag [in] The Daylight Savings Enabled flag + */ +void datetime_dst_enabled_set(bool flag) +{ + DST_Enabled = flag; +} + +/** + * @brief Get the local DST start and end date range + * @param dst_range [out] The DST range to get + * @return true if successful, false on error + */ +bool datetime_dst_ordinal_range( + uint8_t *start_month, + uint8_t *start_week, + uint8_t *start_day, + uint8_t *end_month, + uint8_t *end_week, + uint8_t *end_day) +{ + if (!DST_Range.Ordinal) { + return false; + } + *start_month = DST_Range.Begin_Month; + *start_week = DST_Range.Begin_Week; + *start_day = DST_Range.Begin_Day; + *end_month = DST_Range.End_Month; + *end_week = DST_Range.End_Week; + *end_day = DST_Range.End_Day; + + return true; +} + +bool datetime_dst_ordinal_range_set( + uint8_t start_month, + uint8_t start_week, + BACNET_WEEKDAY start_day, + uint8_t end_month, + uint8_t end_week, + BACNET_WEEKDAY end_day) +{ + DST_Range.Ordinal = true; + DST_Range.Begin_Month = start_month; + DST_Range.Begin_Week = start_week; + DST_Range.Begin_Day = start_day; + DST_Range.End_Month = end_month; + DST_Range.End_Week = end_week; + DST_Range.End_Day = end_day; + + return true; +} + +/** + * @brief Get the local DST start and end date range for specific month day + * @param dst_range [in] The DST range + * @return true if the start and end month day values are returned + */ +bool datetime_dst_date_range( + uint8_t *start_month, + uint8_t *start_day, + uint8_t *end_month, + uint8_t *end_day) +{ + if (DST_Range.Ordinal) { + return false; + } + *start_month = DST_Range.Begin_Month; + *start_day = DST_Range.Begin_Day; + *end_month = DST_Range.End_Month; + *end_day = DST_Range.End_Day; + + return true; +} + +/** + * @brief Set the local DST start and end date range for specific month day + * @param dst_range [in] The DST range to set + * @return true if successful, false on error + */ +bool datetime_dst_date_range_set( + uint8_t start_month, uint8_t start_day, uint8_t end_month, uint8_t end_day) +{ + DST_Range.Ordinal = false; + DST_Range.Begin_Month = start_month; + DST_Range.Begin_Day = start_day; + DST_Range.End_Month = end_month; + DST_Range.End_Day = end_day; + + return true; +} + +/** + * @brief Set the local date and time from a BACnet TimeSynchronization request + * @param bdate [in] The date to set + * @param btime [in] The time to set + * @param utc [in] true if originating from an UTCTimeSynchronization request + */ +void datetime_timesync(BACNET_DATE *bdate, BACNET_TIME *btime, bool utc) +{ + BACNET_DATE_TIME local_time = { 0 }; + const int32_t dst_adjust_minutes = 60L; + + if (utc) { + if (bdate && btime) { + datetime_copy_date(&local_time.date, bdate); + datetime_copy_time(&local_time.time, btime); + datetime_add_minutes(&local_time, UTC_Offset_Minutes); + if (datetime_dst_active(&DST_Range, &local_time, DST_Enabled)) { + datetime_add_minutes(&local_time, dst_adjust_minutes); + } + datetime_copy(&BACnet_Date_Time, &local_time); + mstimer_restart(&Date_Timer); + } + } else { + datetime_copy_date(&BACnet_Date_Time.date, bdate); + datetime_copy_time(&BACnet_Date_Time.time, btime); + mstimer_restart(&Date_Timer); + } +} + +/** + * @brief Initialize the local date and time timer + */ +void datetime_init(void) +{ + dst_init_defaults(&DST_Range); + mstimer_set(&Date_Timer, 0); +} diff --git a/src/bacnet/basic/sys/days.c b/src/bacnet/basic/sys/days.c index afe6d6a2..95b4b049 100644 --- a/src/bacnet/basic/sys/days.c +++ b/src/bacnet/basic/sys/days.c @@ -221,6 +221,8 @@ days_since_epoch(uint16_t epoch_year, uint16_t year, uint8_t month, uint8_t day) days += days_per_month(year, mm); } days += day; + /* 'days since' is one less */ + days -= 1; } return (days); @@ -243,15 +245,15 @@ void days_since_epoch_to_date( uint8_t *pDay) { uint8_t month = 1; - uint8_t day = 0; + uint8_t day = 1; uint16_t year; year = epoch_year; - while (days > days_per_year(year)) { + while (days >= days_per_year(year)) { days -= days_per_year(year); year++; } - while (days > days_per_month(year, month)) { + while (days >= days_per_month(year, month)) { days -= days_per_month(year, month); month++; } @@ -291,3 +293,19 @@ bool days_date_is_valid(uint16_t year, uint8_t month, uint8_t day) return (valid); } + +/** + * Returns the day of the week value + * + * @param epoch_day - day of week for epoch day + * @param days - number of days since epoch + * @return day of week 1..7 offset by epoch day + */ +uint8_t days_of_week(uint8_t epoch_day, uint32_t days) +{ + uint8_t dow = epoch_day; + + dow += (days % 7); + + return dow; +} diff --git a/src/bacnet/basic/sys/days.h b/src/bacnet/basic/sys/days.h index 1462f293..1766b81c 100644 --- a/src/bacnet/basic/sys/days.h +++ b/src/bacnet/basic/sys/days.h @@ -48,6 +48,9 @@ void days_since_epoch_to_date( uint8_t *pMonth, uint8_t *pDay); +BACNET_STACK_EXPORT +uint8_t days_of_week(uint8_t epoch_day, uint32_t days); + BACNET_STACK_EXPORT bool days_date_is_valid(uint16_t year, uint8_t month, uint8_t day); diff --git a/src/bacnet/basic/sys/dst.c b/src/bacnet/basic/sys/dst.c new file mode 100644 index 00000000..64ba7d5d --- /dev/null +++ b/src/bacnet/basic/sys/dst.c @@ -0,0 +1,229 @@ +/** + * @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; + } +} diff --git a/src/bacnet/basic/sys/dst.h b/src/bacnet/basic/sys/dst.h new file mode 100644 index 00000000..a6c27c52 --- /dev/null +++ b/src/bacnet/basic/sys/dst.h @@ -0,0 +1,58 @@ +/** + * @file + * @brief This file contains the function prototypes for for the module. + * @author Steve Karg + * @date 1997 + * @note Public domain algorithms from ACM + * @copyright SPDX-License-Identifier: CC-PDDC + */ +#ifndef BACNET_BASIC_SYS_DST_H +#define BACNET_BASIC_SYS_DST_H +#include +#include +/* BACnet Stack defines - first */ +#include "bacnet/bacdef.h" + +struct daylight_savings_data { + bool Ordinal : 1; + uint8_t Begin_Month; + uint8_t Begin_Day; + uint8_t Begin_Week; + uint8_t End_Month; + uint8_t End_Day; + uint8_t End_Week; + uint16_t Epoch_Year; + uint8_t Epoch_Day; +}; + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +bool dst_active( + struct daylight_savings_data *dst, + uint16_t year, + uint8_t month, + uint8_t day, + uint8_t hour, + uint8_t minute, + uint8_t second); +void dst_init( + struct daylight_savings_data *data, + bool automatic, + 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); +/* initialization */ +void dst_init_defaults(struct daylight_savings_data *data); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif diff --git a/src/bacnet/datalink/dlmstp.c b/src/bacnet/datalink/dlmstp.c index a259eb9d..9d4698f5 100644 --- a/src/bacnet/datalink/dlmstp.c +++ b/src/bacnet/datalink/dlmstp.c @@ -884,7 +884,6 @@ void dlmstp_set_frame_rx_start_callback(dlmstp_hook_frame_rx_start_cb cb_func) void dlmstp_reset_statistics(void) { struct dlmstp_user_data_t *user; - struct dlmstp_statistics *stats; if (!MSTP_Port) { return; diff --git a/src/bacnet/datetime.c b/src/bacnet/datetime.c index 2a9f0290..c7a41489 100644 --- a/src/bacnet/datetime.c +++ b/src/bacnet/datetime.c @@ -141,21 +141,12 @@ uint32_t datetime_ymd_to_days_since_epoch(uint16_t year, uint8_t month, uint8_t day) { uint32_t days = 0; /* return value */ - uint16_t years = 0; /* loop counter for years */ if (datetime_ymd_is_valid(year, month, day)) { - for (years = BACNET_DATE_YEAR_EPOCH; years < year; years++) { - days += 365; - if (days_is_leap_year(years)) { - days++; - } - } - days += datetime_ymd_day_of_year(year, month, day); - /* 'days since' is one less */ - days -= 1; + days = days_since_epoch(BACNET_DATE_YEAR_EPOCH, year, month, day); } - return (days); + return days; } /** @@ -248,11 +239,9 @@ void datetime_days_since_epoch_into_date(uint32_t days, BACNET_DATE *bdate) */ uint8_t datetime_day_of_week(uint16_t year, uint8_t month, uint8_t day) { - uint8_t dow = (uint8_t)BACNET_DAY_OF_WEEK_EPOCH; - - dow += (datetime_ymd_to_days_since_epoch(year, month, day) % 7); - - return dow; + return days_of_week( + BACNET_DAY_OF_WEEK_EPOCH, + datetime_ymd_to_days_since_epoch(year, month, day)); } /** diff --git a/src/bacnet/datetime.h b/src/bacnet/datetime.h index 78192b92..969ef835 100644 --- a/src/bacnet/datetime.h +++ b/src/bacnet/datetime.h @@ -318,18 +318,45 @@ bool datetime_local( BACNET_TIME *btime, int16_t *utc_offset_minutes, bool *dst_active); +/* UTC Offset API */ BACNET_STACK_EXPORT int16_t datetime_utc_offset_minutes(void); BACNET_STACK_EXPORT bool datetime_utc_offset_minutes_set(int16_t minutes); +/* Daylight Savings Time API */ BACNET_STACK_EXPORT -bool datetime_dst_active(void); +bool datetime_dst_enabled(void); BACNET_STACK_EXPORT -bool datetime_dst_range(BACNET_DATE_RANGE *dst_range); +void datetime_dst_enabled_set(bool flag); BACNET_STACK_EXPORT -bool datetime_dst_range_set(BACNET_DATE_RANGE *dst_range); +bool datetime_dst_ordinal_range( + uint8_t *start_month, + uint8_t *start_week, + uint8_t *start_day, + uint8_t *end_month, + uint8_t *end_week, + uint8_t *end_day); +BACNET_STACK_EXPORT +bool datetime_dst_ordinal_range_set( + uint8_t start_month, + uint8_t start_week, + BACNET_WEEKDAY start_day, + uint8_t end_month, + uint8_t end_week, + BACNET_WEEKDAY end_day); +BACNET_STACK_EXPORT +bool datetime_dst_date_range( + uint8_t *start_month, + uint8_t *start_day, + uint8_t *end_month, + uint8_t *end_day); +BACNET_STACK_EXPORT +bool datetime_dst_date_range_set( + uint8_t start_month, uint8_t start_day, uint8_t end_month, uint8_t end_day); +/* BACnet TimeSynchronization service handler API */ BACNET_STACK_EXPORT void datetime_timesync(BACNET_DATE *bdate, BACNET_TIME *btime, bool utc); +/* Initialization for integration with a clock */ BACNET_STACK_EXPORT void datetime_init(void); diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 7f95f8a1..09392691 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -171,6 +171,7 @@ list(APPEND testdirs # basic/sys bacnet/basic/sys/color_rgb bacnet/basic/sys/days + bacnet/basic/sys/dst bacnet/basic/sys/lighting_command bacnet/basic/sys/fifo bacnet/basic/sys/filename diff --git a/test/bacnet/basic/sys/color_rgb/src/main.c b/test/bacnet/basic/sys/color_rgb/src/main.c index 6def03eb..216fdf61 100644 --- a/test/bacnet/basic/sys/color_rgb/src/main.c +++ b/test/bacnet/basic/sys/color_rgb/src/main.c @@ -1,12 +1,8 @@ /* @file - * @brief test BACnet integer encode/decode APIs - * @date June 2022 * @brief tests sRGB to and from from CIE xy and brightness API - * - * @section LICENSE - * Copyright (c) 2022 Steve Karg - * - * SPDX-License-Identifier: MIT + * @date June 2022 + * @author Steve Karg + * @copyright SPDX-License-Identifier: MIT */ #include #include diff --git a/test/bacnet/basic/sys/days/src/main.c b/test/bacnet/basic/sys/days/src/main.c index 9c3da8c8..681e0c4f 100644 --- a/test/bacnet/basic/sys/days/src/main.c +++ b/test/bacnet/basic/sys/days/src/main.c @@ -1,16 +1,18 @@ -/* - * Copyright (c) 2021 Steve Karg - * - * SPDX-License-Identifier: MIT - */ - /* @file - * @brief test BACnet integer encode/decode APIs + * @brief tests day of year calculations API + * @date August 2021 + * @author Steve Karg + * @copyright SPDX-License-Identifier: MIT */ - #include #include +/* define our epic beginnings */ +#define BACNET_EPOCH_YEAR 1900 +/* 1/1/1900 is a Monday */ +/* Monday=1..Sunday=7 */ +#define BACNET_EPOCH_DOW 1 + /** * @addtogroup bacnet_tests * @{ @@ -31,9 +33,43 @@ static void test_epoch_conversion_date( days = days_since_epoch(epoch_year, year, month, day); days_since_epoch_to_date( epoch_year, days, &test_year, &test_month, &test_day); - zassert_equal(year, test_year, NULL); - zassert_equal(month, test_month, NULL); - zassert_equal(day, test_day, NULL); + zassert_equal( + year, test_year, "date=%u/%u/%u year=%u test_year=%u", year, month, day, + year, test_year); + zassert_equal( + month, test_month, "date=%u/%u/%u month=%u test_month=%u", year, month, + day, month, test_month); + zassert_equal( + day, test_day, "date=%u/%u/%u day=%u test_day=%u", year, month, day, + day, test_day); +} + +/** + * Unit Test for the day of week based on epoch year and epoch day of week + * @param epoch_year - years after Christ birth (0..9999 AD) + * @param epoch_dow - day of week (1=Monday...7=Sunday) + * @param year - years after Christ birth (0..9999 AD) + * @param month - months (1=Jan...12=Dec) + * @param day - day of month (1-31) + * @param dow - day of week (1=Monday...7=Sunday) + */ +static void test_epoch_conversion_day( + uint16_t epoch_year, + uint8_t epoch_dow, + uint16_t year, + uint8_t month, + uint8_t day, + uint8_t dow) +{ + uint32_t days; + uint16_t test_dow; + + /* conversions of day and date */ + days = days_since_epoch(epoch_year, year, month, day); + test_dow = days_of_week(epoch_dow, days); + zassert_equal( + dow, test_dow, "date=%u/%u/%u dow=%u test_dow=%u", year, month, day, + dow, test_dow); } /** @@ -45,13 +81,28 @@ ZTEST(days_tests, test_days_epoch_conversion) static void test_days_epoch_conversion(void) #endif { - const uint16_t epoch_year = 2000; + const uint16_t epoch_year = BACNET_EPOCH_YEAR; + const uint8_t epoch_day_of_week = BACNET_EPOCH_DOW; test_epoch_conversion_date(epoch_year, 2000, 1, 1); test_epoch_conversion_date(epoch_year, 2048, 2, 28); test_epoch_conversion_date(epoch_year, 2048, 2, 29); test_epoch_conversion_date(epoch_year, 2038, 6, 15); test_epoch_conversion_date(epoch_year, 9999, 12, 31); + + test_epoch_conversion_day( + epoch_year, epoch_day_of_week, epoch_year, 1, 1, epoch_day_of_week); + /* some known day of week (1=Monday...7=Sunday) */ + test_epoch_conversion_day(epoch_year, epoch_day_of_week, 2003, 1, 6, 1); + test_epoch_conversion_day(epoch_year, epoch_day_of_week, 2003, 1, 7, 2); + test_epoch_conversion_day(epoch_year, epoch_day_of_week, 2003, 1, 8, 3); + test_epoch_conversion_day(epoch_year, epoch_day_of_week, 2003, 1, 9, 4); + test_epoch_conversion_day(epoch_year, epoch_day_of_week, 2003, 1, 10, 5); + test_epoch_conversion_day(epoch_year, epoch_day_of_week, 2003, 1, 11, 6); + test_epoch_conversion_day(epoch_year, epoch_day_of_week, 2003, 1, 12, 7); + test_epoch_conversion_day(epoch_year, epoch_day_of_week, 2003, 1, 13, 1); + /* 50th wedding anniversary */ + test_epoch_conversion_day(epoch_year, epoch_day_of_week, 2043, 6, 26, 5); } /** @@ -134,6 +185,42 @@ static void test_days_date_is_valid(void) zassert_equal(days_per_month(0, 0), 0, NULL); } +/** + * Unit Test for the days, checking the date to see if it is a valid date + */ +#if defined(CONFIG_ZTEST_NEW_API) +ZTEST(days_tests, test_days_since_epoch) +#else +static void test_days_since_epoch(void) +#endif +{ + uint32_t days = 0; + uint16_t year = 0, test_year = 0; + uint8_t month = 0, test_month = 0; + uint8_t day = 0, test_day = 0; + + days = days_since_epoch(BACNET_EPOCH_YEAR, BACNET_EPOCH_YEAR, 1, 1); + zassert_equal(days, 0, "days=%lu", (unsigned long)days); + days_since_epoch_to_date(BACNET_EPOCH_YEAR, days, &year, &month, &day); + zassert_equal(year, BACNET_EPOCH_YEAR, NULL); + zassert_equal(month, 1, NULL); + zassert_equal(day, 1, NULL); + + for (year = BACNET_EPOCH_YEAR; year < (BACNET_EPOCH_YEAR + 0xFF); year++) { + for (month = 1; month <= 12; month++) { + for (day = 1; day <= days_per_month(year, month); day++) { + days = days_since_epoch(BACNET_EPOCH_YEAR, year, month, day); + days_since_epoch_to_date( + BACNET_EPOCH_YEAR, days, &test_year, &test_month, + &test_day); + zassert_equal(year, test_year, NULL); + zassert_equal(month, test_month, NULL); + zassert_equal(day, test_day, NULL); + } + } + } +} + /** * Unit Test for days apart, checking the dates to see how many days apart */ @@ -162,6 +249,7 @@ void test_main(void) { ztest_test_suite( days_tests, ztest_unit_test(test_days_epoch_conversion), + ztest_unit_test(test_days_since_epoch), ztest_unit_test(test_days_of_year_to_md), ztest_unit_test(test_days_date_is_valid), ztest_unit_test(test_days_apart)); diff --git a/test/bacnet/basic/sys/dst/CMakeLists.txt b/test/bacnet/basic/sys/dst/CMakeLists.txt new file mode 100644 index 00000000..c111dabf --- /dev/null +++ b/test/bacnet/basic/sys/dst/CMakeLists.txt @@ -0,0 +1,42 @@ +# SPDX-License-Identifier: MIT + +cmake_minimum_required(VERSION 3.10 FATAL_ERROR) + +get_filename_component(basename ${CMAKE_CURRENT_SOURCE_DIR} NAME) +project(test_${basename} + VERSION 1.0.0 + LANGUAGES C) + + +string(REGEX REPLACE + "/test/bacnet/[a-zA-Z_/-]*$" + "/src" + SRC_DIR + ${CMAKE_CURRENT_SOURCE_DIR}) +string(REGEX REPLACE + "/test/bacnet/[a-zA-Z_/-]*$" + "/test" + TST_DIR + ${CMAKE_CURRENT_SOURCE_DIR}) +set(ZTST_DIR "${TST_DIR}/ztest/src") + +add_compile_definitions( + BIG_ENDIAN=0 + CONFIG_ZTEST=1 + ) + +include_directories( + ${SRC_DIR} + ${TST_DIR}/ztest/include + ) + +add_executable(${PROJECT_NAME} + # File(s) under test + ${SRC_DIR}/bacnet/basic/sys/dst.c + # Support files and stubs (pathname alphabetical) + ${SRC_DIR}/bacnet/basic/sys/days.c + # Test and test library files + ./src/main.c + ${ZTST_DIR}/ztest_mock.c + ${ZTST_DIR}/ztest.c + ) diff --git a/test/bacnet/basic/sys/dst/src/main.c b/test/bacnet/basic/sys/dst/src/main.c new file mode 100644 index 00000000..12cf10b7 --- /dev/null +++ b/test/bacnet/basic/sys/dst/src/main.c @@ -0,0 +1,137 @@ +/* @file + * @brief tests daylight savings time validity API + * @date August 2021 + * @author Steve Karg + * @copyright SPDX-License-Identifier: MIT + */ +#include +#include + +/** + * @addtogroup bacnet_tests + * @{ + */ + +/** + * Unit Test for daylight savings time + */ +#if defined(CONFIG_ZTEST_NEW_API) +ZTEST(dst_tests, dst_test_valid) +#else +static void dst_test_valid(void) +#endif +{ + struct daylight_savings_data data = { 0 }; + uint8_t epoch_day; + uint16_t epoch_year; + /* test at 3am */ + uint8_t hour = 3; + uint8_t minute = 0; + uint8_t second = 0; + bool active; + + unsigned i; + struct dst_test_data { + uint16_t year; + uint8_t month; + uint8_t day; + bool active; + } test_ordinal_data[] = { + /* start date boundary checking for several years */ + { 2007, 3, 10, false }, + { 2007, 3, 11, true }, + { 2008, 3, 8, false }, + { 2008, 3, 9, true }, + { 2009, 3, 7, false }, + { 2009, 3, 8, true }, + { 2010, 3, 13, false }, + { 2010, 3, 14, true }, + { 2011, 3, 12, false }, + { 2011, 3, 13, true }, + { 2012, 3, 10, false }, + { 2012, 3, 11, true }, + { 2013, 3, 9, false }, + { 2013, 3, 10, true }, + { 2014, 3, 8, false }, + { 2014, 3, 9, true }, + { 2015, 3, 7, false }, + { 2015, 3, 8, true }, + /* end date boundary checking for several years */ + { 2007, 11, 3, true }, + { 2007, 11, 4, false }, + { 2008, 11, 1, true }, + { 2008, 11, 2, false }, + { 2009, 10, 31, true }, + { 2009, 11, 1, false }, + { 2010, 11, 6, true }, + { 2010, 11, 7, false }, + { 2011, 11, 5, true }, + { 2011, 11, 6, false }, + { 2012, 11, 3, true }, + { 2012, 11, 4, false }, + { 2013, 11, 2, true }, + { 2013, 11, 3, false }, + { 2014, 11, 1, true }, + { 2014, 11, 2, false }, + { 2015, 10, 31, true }, + { 2015, 11, 1, false }, + /* year long check boundaries over a year */ + { 2013, 1, 1, false }, + { 2013, 3, 3, false }, + { 2013, 3, 7, false }, + { 2013, 3, 8, false }, + { 2013, 3, 9, false }, + { 2013, 3, 10, true }, + { 2013, 3, 11, true }, + { 2013, 3, 12, true }, + { 2013, 7, 10, true }, + { 2013, 11, 2, true }, + { 2013, 11, 3, false }, + { 2013, 11, 4, false }, + { 2013, 11, 7, false }, + { 2013, 11, 8, false }, + { 2013, 11, 30, false }, + { 2013, 12, 31, false }, + }; + struct dst_test_data *td; + + dst_init_defaults(&data); + for (i = 0; i < ARRAY_SIZE(test_ordinal_data); i++) { + td = &test_ordinal_data[i]; + active = dst_active( + &data, td->year, td->month, td->day, hour, minute, second); + zassert_true(active == td->active, NULL); + } + /* test the fixed dates */ + epoch_day = data.Epoch_Day; + epoch_year = data.Epoch_Year; + dst_init(&data, false, 4, 1, 0, 9, 30, 0, epoch_day, epoch_year); + /* check the boundaries */ + active = dst_active(&data, 2013, 3, 31, hour, minute, second); + zassert_true(active == false, NULL); + active = dst_active(&data, 2013, 4, 1, hour, minute, second); + zassert_true(active == true, NULL); + active = dst_active(&data, 2013, 4, 2, hour, minute, second); + zassert_true(active == true, NULL); + active = dst_active(&data, 2013, 9, 29, hour, minute, second); + zassert_true(active == true, NULL); + active = dst_active(&data, 2013, 9, 30, hour, minute, second); + zassert_true(active == false, NULL); + active = dst_active(&data, 2013, 10, 1, hour, minute, second); + zassert_true(active == false, NULL); +} + +/** + * @} + */ + +#if defined(CONFIG_ZTEST_NEW_API) +ZTEST_SUITE(dst_tests, NULL, NULL, NULL, NULL, NULL); +#else +void test_main(void) +{ + ztest_test_suite(dst_tests, ztest_unit_test(dst_test_valid)); + + ztest_run_test_suite(dst_tests); +} +#endif diff --git a/test/bacnet/datetime/src/main.c b/test/bacnet/datetime/src/main.c index a5bbb25f..6bcd4d95 100644 --- a/test/bacnet/datetime/src/main.c +++ b/test/bacnet/datetime/src/main.c @@ -22,8 +22,6 @@ /* define our epic beginnings */ #define BACNET_EPOCH_YEAR 1900 -/* 1/1/1900 is a Monday */ -#define BACNET_EPOCH_DOW BACNET_WEEKDAY_MONDAY /** * @addtogroup bacnet_tests @@ -423,35 +421,6 @@ static void testDateEpochConversion(void) BACNET_EPOCH_YEAR + 0xFF - 1, 12, 31, 23, 59, 59, 0); } -static void testDateEpoch(void) -{ - uint32_t days = 0; - uint16_t year = 0, test_year = 0; - uint8_t month = 0, test_month = 0; - uint8_t day = 0, test_day = 0; - - days = days_since_epoch(BACNET_EPOCH_YEAR, BACNET_EPOCH_YEAR, 1, 1); - zassert_equal(days, 1, "days=%lu", (unsigned long)days); - days_since_epoch_to_date(BACNET_EPOCH_YEAR, days, &year, &month, &day); - zassert_equal(year, BACNET_EPOCH_YEAR, NULL); - zassert_equal(month, 1, NULL); - zassert_equal(day, 1, NULL); - - for (year = BACNET_EPOCH_YEAR; year < (BACNET_EPOCH_YEAR + 0xFF); year++) { - for (month = 1; month <= 12; month++) { - for (day = 1; day <= days_per_month(year, month); day++) { - days = days_since_epoch(BACNET_EPOCH_YEAR, year, month, day); - days_since_epoch_to_date( - BACNET_EPOCH_YEAR, days, &test_year, &test_month, - &test_day); - zassert_equal(year, test_year, NULL); - zassert_equal(month, test_month, NULL); - zassert_equal(day, test_day, NULL); - } - } - } -} - #if defined(CONFIG_ZTEST_NEW_API) ZTEST(bacnet_datetime, testBACnetDayOfWeek) #else @@ -641,11 +610,6 @@ ZTEST_SUITE(bacnet_datetime, NULL, NULL, NULL, NULL, NULL); #else void test_main(void) { -#if 0 - ztest_unit_test(testDateEpoch), - ztest_unit_test(testBACnetDateTimeSeconds), - ztest_unit_test(testDayOfYear), -#endif ztest_test_suite( bacnet_datetime, ztest_unit_test(testBACnetDate), ztest_unit_test(testBACnetTime), ztest_unit_test(testBACnetDateTime), @@ -654,7 +618,7 @@ void test_main(void) ztest_unit_test(testBACnetDateTimeAdd), ztest_unit_test(testBACnetDateTimeWildcard), ztest_unit_test(testDatetimeCodec), - ztest_unit_test(testWildcardDateTime), ztest_unit_test(testDateEpoch), + ztest_unit_test(testWildcardDateTime), ztest_unit_test(testBACnetDateTimeSeconds), ztest_unit_test(testDayOfYear), ztest_unit_test(testDatetimeConvertUTC));