From 0ce6368b434cb1cf2c996042987bfc4502007329 Mon Sep 17 00:00:00 2001 From: Steve Karg Date: Sat, 4 Apr 2020 11:23:26 -0500 Subject: [PATCH] Feature/refactor datetime os dependency (#63) * remove dependency on OS for time functions. * add datetime epoch to and from seconds (yikes! 64-bit!). Add symmetric midnight seconds for unit test. Add unit tests. * clean up BACnet date time warnings * fix BACnet datetime warnings Co-authored-by: Steve Karg --- apps/gateway/main.c | 5 - ports/win32/bacport.h | 5 - .../basic/object/client/device-client.c | 74 ++--------- src/bacnet/basic/object/device.c | 10 +- src/bacnet/basic/object/lc.c | 27 +--- src/bacnet/datetime.c | 116 ++++++++++++++++++ src/bacnet/datetime.h | 13 ++ 7 files changed, 142 insertions(+), 108 deletions(-) diff --git a/apps/gateway/main.c b/apps/gateway/main.c index 56d7998f..046aac9f 100644 --- a/apps/gateway/main.c +++ b/apps/gateway/main.c @@ -98,13 +98,8 @@ static void Devices_Init(uint32_t first_object_instance) /* Now initialize the remote Device objects. */ for (i = 1; i < MAX_NUM_DEVICES; i++) { -#ifdef _MSC_VER - _snprintf(nameText, MAX_DEV_NAME_LEN, "%s %d", DEV_NAME_BASE, i + 1); - _snprintf(descText, MAX_DEV_DESC_LEN, "%s %d", DEV_DESCR_REMOTE, i); -#else snprintf(nameText, MAX_DEV_NAME_LEN, "%s %d", DEV_NAME_BASE, i + 1); snprintf(descText, MAX_DEV_DESC_LEN, "%s %d", DEV_DESCR_REMOTE, i); -#endif characterstring_init_ansi(&name_string, nameText); Add_Routed_Device((first_object_instance + i), &name_string, descText); diff --git a/ports/win32/bacport.h b/ports/win32/bacport.h index d0eee221..6d919714 100644 --- a/ports/win32/bacport.h +++ b/ports/win32/bacport.h @@ -63,11 +63,6 @@ and globals in favor of more secure versions. */ #include #endif #include -#if defined(__BORLANDC__) || defined(_WIN32) -/* seems to not be defined in time.h as specified by The Open Group */ -/* difference from UTC and local standard time */ -extern long int timezone; -#endif #ifdef _MSC_VER #define inline __inline diff --git a/src/bacnet/basic/object/client/device-client.c b/src/bacnet/basic/object/client/device-client.c index 191190aa..47dde0a0 100644 --- a/src/bacnet/basic/object/client/device-client.c +++ b/src/bacnet/basic/object/client/device-client.c @@ -31,7 +31,6 @@ #include #include #include /* for memmove */ -#include /* for timezone, localtime */ /* OS specific include*/ #include "bacport.h" #include "bacnet/basic/sys/mstimer.h" @@ -41,6 +40,7 @@ #include "bacnet/bacenum.h" #include "bacnet/bacapp.h" #include "bacnet/config.h" /* the custom stuff */ +#include "bacnet/datetime.h" #include "bacnet/apdu.h" #include "bacnet/rp.h" /* ReadProperty handling */ #include "bacnet/version.h" @@ -53,12 +53,6 @@ /* include the device object */ #include "bacnet/basic/object/device.h" /* me */ -#if defined(__BORLANDC__) || defined(_WIN32) -/* seems to not be defined in time.h as specified by The Open Group */ -/* difference from UTC and local standard time */ -long int timezone; -#endif - /* note: you really only need to define variables for properties that are writable or that may change. The properties that are constant can be hard coded @@ -83,14 +77,10 @@ static char *Description = "command line client"; /* static uint8_t Max_Segments_Accepted = 0; */ /* VT_Classes_Supported */ /* Active_VT_Sessions */ -static BACNET_TIME Local_Time; /* rely on OS, if there is one */ -static BACNET_DATE Local_Date; /* rely on OS, if there is one */ -/* NOTE: BACnet UTC Offset is inverse of common practice. - If your UTC offset is -5hours of GMT, - then BACnet UTC offset is +5hours. - BACnet UTC offset is expressed in minutes. */ -static int32_t UTC_Offset = 5 * 60; -static bool Daylight_Savings_Status = false; /* rely on OS */ +static BACNET_TIME Local_Time; +static BACNET_DATE Local_Date; +static int16_t UTC_Offset; +static bool Daylight_Savings_Status; #if defined(BACNET_TIME_MASTER) static bool Align_Intervals; static uint32_t Interval_Minutes; @@ -696,57 +686,8 @@ bool Device_Object_Name_Copy(BACNET_OBJECT_TYPE object_type, static void Update_Current_Time(void) { - struct tm *tblock = NULL; -#if defined(_MSC_VER) - time_t tTemp; -#else - struct timeval tv; -#endif -/* -struct tm - -int tm_sec Seconds [0,60]. -int tm_min Minutes [0,59]. -int tm_hour Hour [0,23]. -int tm_mday Day of month [1,31]. -int tm_mon Month of year [0,11]. -int tm_year Years since 1900. -int tm_wday Day of week [0,6] (Sunday =0). -int tm_yday Day of year [0,365]. -int tm_isdst Daylight Savings flag. -*/ -#if defined(_MSC_VER) - time(&tTemp); - tblock = (struct tm *)localtime(&tTemp); -#else - if (gettimeofday(&tv, NULL) == 0) { - tblock = (struct tm *)localtime((const time_t *)&tv.tv_sec); - } -#endif - - if (tblock) { - datetime_set_date(&Local_Date, (uint16_t)tblock->tm_year + 1900, - (uint8_t)tblock->tm_mon + 1, (uint8_t)tblock->tm_mday); -#if !defined(_MSC_VER) - datetime_set_time(&Local_Time, (uint8_t)tblock->tm_hour, - (uint8_t)tblock->tm_min, (uint8_t)tblock->tm_sec, - (uint8_t)(tv.tv_usec / 10000)); -#else - datetime_set_time(&Local_Time, (uint8_t)tblock->tm_hour, - (uint8_t)tblock->tm_min, (uint8_t)tblock->tm_sec, 0); -#endif - if (tblock->tm_isdst) { - Daylight_Savings_Status = true; - } else { - Daylight_Savings_Status = false; - } - /* note: timezone is declared in stdlib. */ - UTC_Offset = timezone / 60; - } else { - datetime_date_wildcard_set(&Local_Date); - datetime_time_wildcard_set(&Local_Time); - Daylight_Savings_Status = false; - } + datetime_local( + &Local_Date, &Local_Time, &UTC_Offset, &Daylight_Savings_Status); } void Device_getCurrentDateTime(BACNET_DATE_TIME *DateTime) @@ -1099,6 +1040,7 @@ void Device_Init(object_functions_t *object_table) struct object_functions *pObject = NULL; characterstring_init_ansi(&My_Object_Name, "SimpleClient"); + datetime_init(); /* we don't use the object table passed in */ (void)object_table; pObject = &Object_Table[0]; diff --git a/src/bacnet/basic/object/device.c b/src/bacnet/basic/object/device.c index 917a6ea8..aedd2d53 100644 --- a/src/bacnet/basic/object/device.c +++ b/src/bacnet/basic/object/device.c @@ -29,12 +29,12 @@ #include #include #include /* for memmove */ -#include /* for timezone, localtime */ #include "bacnet/bacdef.h" #include "bacnet/bacdcode.h" #include "bacnet/bacenum.h" #include "bacnet/bacapp.h" #include "bacnet/config.h" /* the custom stuff */ +#include "bacnet/datetime.h" #include "bacnet/apdu.h" #include "bacnet/wp.h" /* WriteProperty handling */ #include "bacnet/rp.h" /* ReadProperty handling */ @@ -79,12 +79,6 @@ #include "bacnet/basic/ucix/ucix.h" #endif /* defined(BAC_UCI) */ -#if defined(__BORLANDC__) || defined(_WIN32) -/* Not included in time.h as specified by The Open Group */ -/* Difference from UTC and local standard time */ -long int timezone; -#endif - /* local forward (semi-private) and external prototypes */ int Device_Read_Property_Local(BACNET_READ_PROPERTY_DATA *rpdata); bool Device_Write_Property_Local(BACNET_WRITE_PROPERTY_DATA *wp_data); @@ -1838,7 +1832,7 @@ void Device_Init(object_functions_t *object_table) } ucix_cleanup(ctx); #endif /* defined(BAC_UCI) */ - + datetime_init(); if (object_table) { Object_Table = object_table; } else { diff --git a/src/bacnet/basic/object/lc.c b/src/bacnet/basic/object/lc.c index 17f1e0f2..f3560c81 100644 --- a/src/bacnet/basic/object/lc.c +++ b/src/bacnet/basic/object/lc.c @@ -30,7 +30,6 @@ #include #include #include /* for memcpy */ -#include #include "bacnet/bacdef.h" #include "bacnet/bacdcode.h" #include "bacnet/datetime.h" @@ -260,29 +259,9 @@ bool Load_Control_Object_Name( static void Update_Current_Time(BACNET_DATE_TIME *bdatetime) { - time_t timer; - struct tm *tblock; - - /* - struct tm { - int tm_sec; - int tm_min; - int tm_hour; - int tm_mday; - int tm_mon; - int tm_year; - int tm_wday; - int tm_yday; - int tm_isdst; - }; - */ - - timer = time(NULL); - tblock = localtime(&timer); - datetime_set_values(bdatetime, (uint16_t)tblock->tm_year, - (uint8_t)tblock->tm_mon, (uint8_t)tblock->tm_mday, - (uint8_t)tblock->tm_hour, (uint8_t)tblock->tm_min, - (uint8_t)tblock->tm_sec, 0); + if (bdatetime) { + datetime_local(&bdatetime->date, &bdatetime->time, NULL, NULL); + } } /* convert the shed level request into an Analog Output Present_Value */ diff --git a/src/bacnet/datetime.c b/src/bacnet/datetime.c index 92e9fcb9..d14e238d 100644 --- a/src/bacnet/datetime.c +++ b/src/bacnet/datetime.c @@ -534,6 +534,22 @@ static void seconds_since_midnight_into_hms( } } +/** + * @brief Converts the number of seconds since midnight into BACnet Time + * @param seconds since midnight + * @param btime [in] BACNET_TIME containing the time to convert + */ +void datetime_seconds_since_midnight_into_time( + uint32_t seconds, + BACNET_TIME *btime) +{ + if (btime) { + seconds_since_midnight_into_hms(seconds, + &btime->hour, &btime->min, &btime->sec); + btime->hundredths = 0; + } +} + /** Calculates the number of seconds since midnight * * @param btime [in] BACNET_TIME containing the time to convert @@ -617,6 +633,54 @@ void datetime_add_minutes(BACNET_DATE_TIME *bdatetime, int32_t minutes) datetime_days_since_epoch_into_date(bdatetime_days, &bdatetime->date); } +#ifdef UINT64_MAX +/** + * @brief Calculates the number of seconds since epoch + * @param bdatetime [in] the starting date and time + * @return seconds since midnight + */ +uint64_t datetime_seconds_since_epoch( + BACNET_DATE_TIME * bdatetime) +{ + uint64_t seconds = 0; + uint32_t days = 0; + + if (bdatetime) { + days = datetime_days_since_epoch(&bdatetime->date); + seconds = seconds_since_midnight(24, 0, 0); + seconds *= days; + seconds += datetime_seconds_since_midnight(&bdatetime->time); + } + + return seconds; +} +#endif + +#ifdef UINT64_MAX +/** + * @brief Calculates the number of seconds since epoch + * @param bdatetime [in] the starting date and time + * @return seconds since midnight + */ +void datetime_since_epoch_seconds( + BACNET_DATE_TIME * bdatetime, + uint64_t seconds) +{ + uint32_t seconds_after_midnight = 0; + uint32_t days = 0; + uint32_t day_seconds = 0; + + if (bdatetime) { + day_seconds = seconds_since_midnight(24, 0, 0); + days = seconds / day_seconds; + seconds_after_midnight = seconds - (days * day_seconds); + datetime_seconds_since_midnight_into_time( + seconds_after_midnight, &bdatetime->time); + datetime_days_since_epoch_into_date(days, &bdatetime->date); + } +} +#endif + /* Returns true if year is a wildcard */ bool datetime_wildcard_year(BACNET_DATE *bdate) { @@ -1011,6 +1075,20 @@ int bacapp_decode_context_datetime( #include #include "ctest.h" +static void datetime_print(const char *title, + BACNET_DATE_TIME *bdatetime) +{ + printf("%s: %04u/%02u/%02u %02u:%02u:%02u.%03u\n", + title, + (unsigned int)bdatetime->date.year, + (unsigned int)bdatetime->date.month, + (unsigned int)bdatetime->date.wday, + (unsigned int)bdatetime->time.hour, + (unsigned int)bdatetime->time.min, + (unsigned int)bdatetime->time.sec, + (unsigned int)bdatetime->time.hundredths); +} + static void testBACnetDateTimeWildcard(Test *pTest) { BACNET_DATE_TIME bdatetime; @@ -1323,6 +1401,42 @@ static void testDayOfYear(Test *pTest) } } +static void testDateEpochConversionCompare(Test *pTest, + uint16_t year, uint8_t month, uint8_t day, + uint8_t hour, uint8_t minute, uint8_t second, uint8_t hundredth) +{ + uint64_t epoch_seconds = 0; + BACNET_DATE_TIME bdatetime = {0}; + BACNET_DATE_TIME test_bdatetime = {0}; + int compare = 0; + + datetime_set_date(&bdatetime.date, year, month, day); + datetime_set_time(&bdatetime.time, hour, minute, second, + hundredth); + epoch_seconds = datetime_seconds_since_epoch(&bdatetime); + datetime_since_epoch_seconds(&test_bdatetime, + epoch_seconds); + compare = datetime_compare(&bdatetime,&test_bdatetime); + ct_test(pTest,compare == 0); + if (compare != 0) { + datetime_print("bdatetime", &bdatetime); + datetime_print("test_bdatetime", &test_bdatetime); + } +} + +static void testDateEpochConversion(Test *pTest) +{ + /* min */ + testDateEpochConversionCompare(pTest, + BACNET_EPOCH_YEAR, 1, 1, 0, 0, 0, 0); + /* middle */ + testDateEpochConversionCompare(pTest, + 2020, 6, 26, 12, 30, 30, 0); + /* max */ + testDateEpochConversionCompare(pTest, + BACNET_EPOCH_YEAR + 0xFF - 1, 12, 31, 23, 59, 59, 0); +} + static void testDateEpoch(Test *pTest) { uint32_t days = 0; @@ -1479,6 +1593,8 @@ void testDateTime(Test *pTest) assert(rc); rc = ct_addTestFunction(pTest, testDateEpoch); assert(rc); + rc = ct_addTestFunction(pTest, testDateEpochConversion); + assert(rc); rc = ct_addTestFunction(pTest, testBACnetDateTimeSeconds); assert(rc); rc = ct_addTestFunction(pTest, testBACnetDateTimeAdd); diff --git a/src/bacnet/datetime.h b/src/bacnet/datetime.h index c00c0a4e..2dce50e3 100644 --- a/src/bacnet/datetime.h +++ b/src/bacnet/datetime.h @@ -149,6 +149,11 @@ extern "C" { uint16_t year, uint8_t month, uint8_t day); + + BACNET_STACK_EXPORT + void datetime_seconds_since_midnight_into_time( + uint32_t seconds, + BACNET_TIME *btime); BACNET_STACK_EXPORT uint32_t datetime_seconds_since_midnight( BACNET_TIME * btime); @@ -208,6 +213,14 @@ extern "C" { BACNET_DATE_TIME * bdatetime, int32_t minutes); + BACNET_STACK_EXPORT + uint64_t datetime_seconds_since_epoch( + BACNET_DATE_TIME * bdatetime); + BACNET_STACK_EXPORT + void datetime_since_epoch_seconds( + BACNET_DATE_TIME * bdatetime, + uint64_t seconds); + /* date and time wildcards */ BACNET_STACK_EXPORT bool datetime_wildcard_year(