Merge pull request #5363 from mprse/extended_rtc

Add support and tests for extended RTC
pull/5693/head
Martin Kojtal 2017-12-12 17:36:44 +00:00 committed by GitHub
commit be52ba2156
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 660 additions and 271 deletions

View File

@ -0,0 +1,138 @@
"""
mbed SDK
Copyright (c) 2011-2013 ARM Limited
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
from mbed_host_tests import BaseHostTest
import time
import calendar
import datetime
class RTC_time_calc_test(BaseHostTest):
"""
This is the host part of the test to verify if:
- _rtc_mktime function converts a calendar time into time since UNIX epoch as a time_t,
- _rtc_localtime function converts a given time in seconds since epoch into calendar time.
The same algoritm to generate next calendar time to be tested is used by both parts of the test.
We will check if correct time since UNIX epoch is calculated for the first and the last day
of each month and across valid years.
Mbed part of the test sends calculated time since UNIX epoch.
This part validates given value and responds to indicate pass or fail.
Additionally it sends also encoded day of week and day of year which
will be needed to verify _rtc_localtime.
Support for both types of RTC devices is provided:
- RTCs which handles all leap years in the mentioned year range correctly. Leap year is determined by checking if
the year counter value is divisible by 400, 100, and 4. No problem here.
- RTCs which handles leap years correctly up to 2100. The RTC does a simple bit comparison to see if the two
lowest order bits of the year counter are zero. In this case 2100 year will be considered
incorrectly as a leap year, so the last valid point in time will be 28.02.2100 23:59:59 and next day will be
29.02.2100 (invalid). So after 28.02.2100 the day counter will be off by a day.
"""
edge_date = datetime.datetime(2100, 2, 28, 0, 0, 0)
years = [1970, 1971, 1972, 1973, 1974, 1975, 1976, 1977, 1978, 1979, 1980,
2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
2099, 2100, 2101, 2102, 2103, 2104, 2105, 2106]
year_id = 0
full_leap_year_support = False
RTC_FULL_LEAP_YEAR_SUPPORT = 0
RTC_PARTIAL_LEAP_YEAR_SUPPORT = 1
def _set_leap_year_support(self, key, value, timestamp):
if (int(value) == self.RTC_FULL_LEAP_YEAR_SUPPORT):
self.full_leap_year_support = True
else:
self.full_leap_year_support = False
self.first = True
self.date = datetime.datetime(1970, 1, 1, 23, 0, 0)
self.year_id = 0
def _verify_timestamp(self, key, value, timestamp):
# week day in python is counted from sunday(0) and on mbed side week day is counted from monday(0).
# year day in python is counted from 1 and on mbed side year day is counted from 0.
week_day = ((self.date.timetuple().tm_wday + 1) % 7)
year_day = self.date.timetuple().tm_yday - 1
# Fix for RTC which not have full leap year support.
if (not self.full_leap_year_support):
if self.date >= self.edge_date:
# After 28.02.2100 we should be one day off - add this day and store original
date_org = self.date
self.date += datetime.timedelta(days = 1)
# Adjust week day.
week_day = ((self.date.timetuple().tm_wday + 1) % 7)
# Adjust year day.
if (self.date.year == 2100):
year_day = self.date.timetuple().tm_yday - 1
else:
year_day = date_org.timetuple().tm_yday - 1
# Last day in year
if (self.date.month == 1 and self.date.day == 1):
if (self.date.year == 2101):
# Exception for year 2100 - ivalid handled by RTC without full leap year support
year_day = 365
else:
year_day = date_org.timetuple().tm_yday - 1
t = (self.date.year , self.date.month, self.date.day, self.date.hour, self.date.minute, self.date.second, 0, 0, 0)
expected_timestamp = calendar.timegm(t)
actual_timestamp = int(value) & 0xffffffff # convert to unsigned int
# encode week day and year day in the response
response = (week_day << 16) | year_day
if (actual_timestamp == expected_timestamp):
# response contains encoded week day and year day
self.send_kv("passed", str(response))
else:
self.send_kv("failed", 0)
print "expected = %d, result = %d" % (expected_timestamp , actual_timestamp)
# calculate next date
if (self.first):
days_range = calendar.monthrange(self.date.year, self.date.month)
self.date = self.date.replace(day = days_range[1], minute = 59, second = 59)
self.first = not self.first
else:
self.date += datetime.timedelta(days = 1)
if (self.date.month == 1):
self.year_id += 1
self.date = self.date.replace(year = self.years[self.year_id])
self.date = self.date.replace(day = 1, minute = 0, second = 0)
self.first = not self.first
def setup(self):
self.register_callback('timestamp', self._verify_timestamp)
self.register_callback('leap_year_setup', self._set_leap_year_support)
def result(self):
return self.__result
def teardown(self):
pass

View File

@ -21,23 +21,15 @@
#include "mbed.h"
#include "mbed_mktime.h"
// Limit the test range to 1935 for IAR only. From the IAR C/C++ Development Guide:
// "The 32-bit interface supports years from 1900 up to 2035 and uses a 32-bit integer
// for time_t."
#ifdef __ICCARM__
#define LOCALTIME_MAX 2082758400 // 1st of january 2036 at 00:00:00
#define MKTIME_YR_MAX 136
#else
#define LOCALTIME_MAX INT_MAX
#define MKTIME_YR_MAX 137
#endif
#define LAST_VALID_YEAR 206
using namespace utest::v1;
/*
* regular is_leap_year, see platform/mbed_mktime.c for the optimized version
*/
bool is_leap_year(int year) {
static rtc_leap_year_support_t rtc_leap_year_support;
/* Regular is_leap_year, see platform/mbed_mktime.c for the optimised version. */
bool is_leap_year(int year)
{
year = 1900 + year;
if (year % 4) {
return false;
@ -49,212 +41,173 @@ bool is_leap_year(int year) {
return true;
}
/*
* Test the optimized version of _rtc_is_leap_year against the generic version.
/* Test the optimised version of _rtc_is_leap_year() against the generic version.
*
* Note: This test case is designed for both types of RTC devices:
* - RTC devices which handle correctly leap years in whole range (1970 - 2106).
* - RTC devices which does not handle correctly leap years in whole range (1970 - 2106).
* This RTC devices uses simpler leap year detection and incorrectly treat 2100 as a leap year.
* rtc_leap_year_support variable specifies which device is tested.
*
* Given is year in valid range.
* When _rtc_is_leap_year() function is called.
* Then _rtc_is_leap_year() returns true if given year is a leap year; false otherwise.
*/
void test_is_leap_year() {
for (int i = 70; i < 138; ++i) {
void test_is_leap_year()
{
for (int i = 70; i <= LAST_VALID_YEAR; ++i) {
bool expected = is_leap_year(i);
bool actual_value = _rtc_is_leap_year(i);
if (expected != actual_value) {
printf ("leap year failed with i = %d\r\n", i);
/* Add exception for year 2100. */
if (rtc_leap_year_support == RTC_4_YEAR_LEAP_YEAR_SUPPORT && i == 200) {
expected = true;
}
bool actual_value = _rtc_is_leap_year(i, rtc_leap_year_support);
if (expected != actual_value) {
printf("Leap year failed with i = %d\r\n", i);
}
TEST_ASSERT_EQUAL(expected, actual_value);
}
}
struct tm make_time_info(int year, int month, int day, int hours, int minutes, int seconds) {
struct tm timeinfo = {
seconds, // tm_sec
minutes, // tm_min
hours, // tm_hour
day, // tm_mday
month, // tm_mon
year, // tm_year
0, // tm_wday
0, // tm_yday
0, // tm_isdst
};
return timeinfo;
}
/* Structure to test border values for _rtc_maketime(). */
typedef struct
{
struct tm timeinfo;
time_t exp_seconds; // if result is false then exp_seconds is irrelevant
bool result;
} test_mk_time_struct;
/*
* test out of range values for _rtc_mktime.
* The function operates from the 1st of january 1970 at 00:00:00 to the 19th
* of january 2038 at 03:14:07.
/* Array which contains data to test boundary values for the RTC devices which handles correctly leap years in
* whole range (1970 - 2106).
* Expected range: the 1st of January 1970 at 00:00:00 (seconds: 0) to the 7th of February 2106 at 06:28:15 (seconds: UINT_MAX).
*/
void test_mk_time_out_of_range() {
tm invalid_lower_bound = make_time_info(
69,
11,
31,
23,
59,
59
);
test_mk_time_struct test_mk_time_arr_full[] = {
{{ 0, 0, 0, 1, 0, 70, 0, 0, 0 }, (time_t) 0, true}, // valid lower bound - the 1st of January 1970 at 00:00:00
{{ 59, 59, 23, 31, 11, 59, 0, 0, 0 }, (time_t) 0, false }, // invalid lower bound - the 31st of December 1969 at 23:59:59
tm valid_lower_bound = make_time_info(
70,
0,
1,
0,
0,
0
);
{{ 15, 28, 6, 7, 1, 206, 0, 0, 0 }, (time_t)(UINT_MAX), true }, // valid upper bound - the 7th of February 2106 at 06:28:15
{{ 16, 28, 6, 7, 1, 206, 0, 0, 0 }, (time_t) 0, false }, // invalid upper bound - the 7th of February 2106 at 06:28:16
};
tm valid_upper_bound = make_time_info(
138,
0,
19,
3,
14,
7
);
tm invalid_upper_bound = make_time_info(
138,
0,
19,
3,
14,
8
);
TEST_ASSERT_EQUAL_INT(((time_t) -1), _rtc_mktime(&invalid_lower_bound));
TEST_ASSERT_EQUAL_INT(((time_t) 0), _rtc_mktime(&valid_lower_bound));
TEST_ASSERT_EQUAL_INT(((time_t) INT_MAX), _rtc_mktime(&valid_upper_bound));
TEST_ASSERT_EQUAL_INT(((time_t) -1), _rtc_mktime(&invalid_upper_bound));
}
/*
* test mktime over a large set of values
/* Array which contains data to test boundary values for the RTC devices which does not handle correctly leap years in
* whole range (1970 - 2106). On this platforms we will be one day off after 28.02.2100 since 2100 year will be
* incorrectly treated as a leap year.
* Expected range: the 1st of January 1970 at 00:00:00 (seconds: 0) to the 6th of February 2106 at 06:28:15 (seconds: UINT_MAX).
*/
void test_mk_time() {
for (size_t year = 70; year < MKTIME_YR_MAX; ++year) {
for (size_t month = 0; month < 12; ++month) {
for (size_t day = 1; day < 32; ++day) {
if (month == 1 && is_leap_year(year) && day == 29) {
break;
} else if(month == 1 && !is_leap_year(year) && day == 28) {
break;
} else if (
day == 31 &&
(month == 3 || month == 5 || month == 8 || month == 10)
) {
break;
}
test_mk_time_struct test_mk_time_arr_partial[] = {
{{ 0, 0, 0, 1, 0, 70, 0, 0, 0 }, (time_t) 0, true}, // valid lower bound - the 1st of January 1970 at 00:00:00
{{ 59, 59, 23, 31, 11, 59, 0, 0, 0 }, (time_t) 0, false }, // invalid lower bound - the 31st of December 1969 at 23:59:59
for (size_t hour = 0; hour < 24; ++hour) {
tm time_info = make_time_info(
year,
month,
day,
hour,
hour % 2 ? 59 : 0,
hour % 2 ? 59 : 0
);
{{ 15, 28, 6, 6, 1, 206, 0, 0, 0 }, (time_t)(UINT_MAX), true }, // valid upper bound - the 6th of February 2106 at 06:28:15
{{ 16, 28, 6, 6, 1, 206, 0, 0, 0 }, (time_t) 0, false }, // invalid upper bound - the 6th of February 2106 at 06:28:16
};
time_t expected = mktime(&time_info);
time_t actual_value = _rtc_mktime(&time_info);
/* Test boundary values for _rtc_maketime().
*
* Note: This test case is designed for both types of RTC devices:
* - RTC devices which handle correctly leap years in whole range (1970 - 2106).
* - RTC devices which does not handle correctly leap years in whole range (1970 - 2106).
* This RTC devices uses simpler leap year detection and incorrectly treat 2100 as a leap year.
* rtc_leap_year_support variable specifies which device is tested.
*
* Given is boundary calendar time.
* When _rtc_maketime() function is called to convert the calendar time into timestamp.
* Then if given calendar time is valid function returns true and conversion result, otherwise returns false.
*/
void test_mk_time_boundary()
{
test_mk_time_struct *pTestCases;
char msg[128] = "";
if (expected != actual_value) {
snprintf(
msg, sizeof(msg),
"year = %d, month = %d, day = %d, diff = %ld",
year, month, day, expected - actual_value
);
}
/* Select array with test cases. */
if (rtc_leap_year_support == RTC_FULL_LEAP_YEAR_SUPPORT) {
pTestCases = test_mk_time_arr_full;
} else {
pTestCases = test_mk_time_arr_partial;
}
TEST_ASSERT_EQUAL_UINT32_MESSAGE(expected, actual_value, msg);
}
}
for (int i = 0; i < (sizeof(test_mk_time_arr_full) / (sizeof(test_mk_time_struct))); i++) {
time_t seconds;
bool result = _rtc_maketime(&pTestCases[i].timeinfo, &seconds, rtc_leap_year_support);
TEST_ASSERT_EQUAL(pTestCases[i].result, result);
/* If the result is false, then we have conversion error - skip checking seconds. */
if (pTestCases[i].result) {
TEST_ASSERT_EQUAL_UINT32(pTestCases[i].exp_seconds, seconds);
}
}
}
/*
* test value out of range for localtime
/* Test _rtc_maketime() function - call with invalid parameters.
*
* Given is _rtc_maketime() function.
* When _rtc_maketime() function is called with invalid parameter.
* Then _rtc_maketime() function returns false.
*/
void test_local_time_limit() {
struct tm dummy_value;
TEST_ASSERT_FALSE(_rtc_localtime((time_t) -1, &dummy_value));
TEST_ASSERT_FALSE(_rtc_localtime((time_t) INT_MIN, &dummy_value));
void test_mk_time_invalid_param()
{
time_t seconds;
struct tm timeinfo;
TEST_ASSERT_EQUAL(false, _rtc_maketime(NULL, &seconds, RTC_FULL_LEAP_YEAR_SUPPORT ));
TEST_ASSERT_EQUAL(false, _rtc_maketime(NULL, &seconds, RTC_4_YEAR_LEAP_YEAR_SUPPORT ));
TEST_ASSERT_EQUAL(false, _rtc_maketime(&timeinfo, NULL, RTC_FULL_LEAP_YEAR_SUPPORT ));
TEST_ASSERT_EQUAL(false, _rtc_maketime(&timeinfo, NULL, RTC_4_YEAR_LEAP_YEAR_SUPPORT ));
}
/*
* test _rtc_localtime over a large set of values.
/* Test _rtc_localtime() function - call with invalid parameters.
*
* Given is _rtc_localtime() function.
* When _rtc_localtime() function is called with invalid parameter.
* Then _rtc_localtime() function returns false.
*/
void test_local_time() {
for (uint32_t i = 0; i < LOCALTIME_MAX; i += 3451) {
time_t copy = (time_t) i;
struct tm* expected = localtime(&copy);
struct tm actual_value;
bool result = _rtc_localtime((time_t) i, &actual_value);
if (
expected->tm_sec != actual_value.tm_sec ||
expected->tm_min != actual_value.tm_min ||
expected->tm_hour != actual_value.tm_hour ||
expected->tm_mday != actual_value.tm_mday ||
expected->tm_mon != actual_value.tm_mon ||
expected->tm_year != actual_value.tm_year ||
expected->tm_wday != actual_value.tm_wday ||
expected->tm_yday != actual_value.tm_yday ||
result == false
) {
printf("error: i = %lu\r\n", i);
}
TEST_ASSERT_TRUE(result);
TEST_ASSERT_EQUAL_UINT32_MESSAGE(
expected->tm_sec, actual_value.tm_sec, "invalid seconds"
);
TEST_ASSERT_EQUAL_UINT32_MESSAGE(
expected->tm_min, actual_value.tm_min, "invalid minutes"
);
TEST_ASSERT_EQUAL_UINT32_MESSAGE(
expected->tm_hour, actual_value.tm_hour, "invalid hours"
);
TEST_ASSERT_EQUAL_UINT32_MESSAGE(
expected->tm_mday, actual_value.tm_mday, "invalid day"
);
TEST_ASSERT_EQUAL_UINT32_MESSAGE(
expected->tm_mon, actual_value.tm_mon, "invalid month"
);
TEST_ASSERT_EQUAL_UINT32_MESSAGE(
expected->tm_year, actual_value.tm_year, "invalid year"
);
TEST_ASSERT_EQUAL_UINT32_MESSAGE(
expected->tm_wday, actual_value.tm_wday, "invalid weekday"
);
TEST_ASSERT_EQUAL_UINT32_MESSAGE(
expected->tm_yday, actual_value.tm_yday, "invalid year day"
);
}
void test_local_time_invalid_param()
{
TEST_ASSERT_EQUAL(false, _rtc_localtime(1, NULL, RTC_FULL_LEAP_YEAR_SUPPORT ));
TEST_ASSERT_EQUAL(false, _rtc_localtime(1, NULL, RTC_4_YEAR_LEAP_YEAR_SUPPORT ));
}
utest::v1::status_t greentea_failure_handler(const Case *const source, const failure_t reason) {
greentea_case_failure_abort_handler(source, reason);
return STATUS_CONTINUE;
utest::v1::status_t teardown_handler_t(const Case * const source, const size_t passed, const size_t failed,
const failure_t reason)
{
return greentea_case_teardown_handler(source, passed, failed, reason);
}
utest::v1::status_t full_leap_year_case_setup_handler_t(const Case * const source, const size_t index_of_case)
{
rtc_leap_year_support = RTC_FULL_LEAP_YEAR_SUPPORT;
return greentea_case_setup_handler(source, index_of_case);
}
utest::v1::status_t partial_leap_year_case_setup_handler_t(const Case * const source, const size_t index_of_case)
{
rtc_leap_year_support = RTC_4_YEAR_LEAP_YEAR_SUPPORT;
return greentea_case_setup_handler(source, index_of_case);
}
Case cases[] = {
Case("test is leap year", test_is_leap_year, greentea_failure_handler),
Case("test mk time out of range values", test_mk_time_out_of_range, greentea_failure_handler),
Case("mk time", test_mk_time, greentea_failure_handler),
Case("test local time", test_local_time, greentea_failure_handler),
Case("test local time limits", test_local_time_limit, greentea_failure_handler),
Case("test is leap year - RTC leap years full support", full_leap_year_case_setup_handler_t, test_is_leap_year, teardown_handler_t),
Case("test is leap year - RTC leap years partial support", partial_leap_year_case_setup_handler_t, test_is_leap_year, teardown_handler_t),
Case("test make time boundary values - RTC leap years full support", full_leap_year_case_setup_handler_t, test_mk_time_boundary, teardown_handler_t),
Case("test make time boundary values - RTC leap years partial support", partial_leap_year_case_setup_handler_t, test_mk_time_boundary, teardown_handler_t),
Case("test make time - invalid param", test_mk_time_invalid_param, teardown_handler_t),
Case("test local time - invalid param", test_local_time_invalid_param, teardown_handler_t),
};
utest::v1::status_t greentea_test_setup(const size_t number_of_cases) {
GREENTEA_SETUP(1200, "default_auto");
utest::v1::status_t greentea_test_setup(const size_t number_of_cases)
{
GREENTEA_SETUP(20, "default_auto");
return greentea_test_setup_handler(number_of_cases);
}
Specification specification(greentea_test_setup, cases, greentea_test_teardown_handler);
int main() {
int main()
{
return Harness::run(specification);
}

View File

@ -0,0 +1,214 @@
/*
* Copyright (c) 2013-2016, ARM Limited, All Rights Reserved
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* This is the mbed device part of the test to verify if:
* - _rtc_maketime() function converts a calendar time into time since UNIX epoch as a time_t,
* - _rtc_localtime() function converts a given time in seconds since epoch into calendar time.
*/
#include "mbed.h"
#include "greentea-client/test_env.h"
#include "utest/utest.h"
#include "unity/unity.h"
#include "mbed_mktime.h"
#define LAST_VALID_YEAR 206
using namespace utest::v1;
static rtc_leap_year_support_t rtc_leap_year_support;
/*
* regular is_leap_year, see platform/mbed_mktime.c for the optimised version
*/
bool is_leap_year(int year)
{
year = 1900 + year;
if (year % 4) {
return false;
} else if (year % 100) {
return true;
} else if (year % 400) {
return false;
}
return true;
}
struct tm make_time_info(int year, int month, int day, int hours, int minutes, int seconds)
{
struct tm timeinfo =
{ seconds, // tm_sec
minutes, // tm_min
hours, // tm_hour
day, // tm_mday
month, // tm_mon
year, // tm_year
0, // tm_wday
0, // tm_yday
0, // tm_isdst
};
return timeinfo;
}
/* Test _rtc_maketime() and _rtc_localtime() across wide range
*
* Note: This test functions handles both types of RTC devices:
* - devices which supports full leap years support in range 1970 - 2106.
* - devices which supports parial leap years support and incorrectly treats 2100 year as a leap year.
*
* Given is valid calendar time.
* When _rtc_maketime() is used to generate timestamp from calendar time and _rtc_localtime() is used to convert
* timestamp to calendar time.
* Then both operations gives valid results.
*/
void test_case_mktime_localtime()
{
char _key[11] =
{ };
char _value[128] =
{ };
size_t years[] = {70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80,
100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110,
199, 200, 201, 202, 203, 204, 205};
/* Inform host part of the test about tested RTC type. */
greentea_send_kv("leap_year_setup", rtc_leap_year_support);
/* Check the first and last last day of each month. */
for (size_t year_id = 0; year_id < (sizeof(years) /sizeof(size_t)) ; ++year_id) {
for (size_t month = 0; month < 12; ++month) {
for (size_t dayid = 0; dayid < 2; ++dayid) {
size_t year = years[year_id];
size_t day = 0;
/* Test the first and the last day of each month:
* day 0 - first,
* day 1 - last
* */
switch (dayid)
{
case 0:
day = 1;
break;
case 1:
day = 31;
if (month == 3 || month == 5 || month == 8 || month == 10) {
day = 30;
}
if (month == 1) {
day = 28;
}
if (month == 1 && is_leap_year(year)) {
day = 29;
}
/* Additional conditions for RTCs with partial leap year support. */
if(month == 1 && year == 200 && rtc_leap_year_support == RTC_4_YEAR_LEAP_YEAR_SUPPORT) {
day = 29;
}
break;
default:
break;
}
tm time_info = make_time_info(year, month, day, 23, dayid ? 59 : 0, dayid ? 59 : 0);
time_t actual_timestamp;
TEST_ASSERT_TRUE(_rtc_maketime(&time_info, &actual_timestamp, rtc_leap_year_support));
greentea_send_kv("timestamp", (int) actual_timestamp);
greentea_parse_kv(_key, _value, sizeof(_key), sizeof(_value));
TEST_ASSERT_EQUAL_STRING("passed", _key);
/* Response which indicates success contains encoded week day
* and year day needed to verify _rtc_localtime().
* Use validated timestamp to generate and validate calendar time.
*/
unsigned int buf = (unsigned int) strtol(_value, NULL, 10);
time_info.tm_wday = ((buf >> 16) & 0x0000FFFF);
time_info.tm_yday = (buf & 0x0000FFFF);
tm actual_time_info;
bool result = _rtc_localtime((time_t) actual_timestamp, &actual_time_info, rtc_leap_year_support);
TEST_ASSERT_TRUE(result);
TEST_ASSERT_EQUAL_UINT32_MESSAGE(time_info.tm_sec, actual_time_info.tm_sec, "invalid seconds");
TEST_ASSERT_EQUAL_UINT32_MESSAGE(time_info.tm_min, actual_time_info.tm_min, "invalid minutes");
TEST_ASSERT_EQUAL_UINT32_MESSAGE(time_info.tm_hour, actual_time_info.tm_hour, "invalid hours");
TEST_ASSERT_EQUAL_UINT32_MESSAGE(time_info.tm_mday, actual_time_info.tm_mday, "invalid day");
TEST_ASSERT_EQUAL_UINT32_MESSAGE(time_info.tm_mon, actual_time_info.tm_mon, "invalid month");
TEST_ASSERT_EQUAL_UINT32_MESSAGE(time_info.tm_year, actual_time_info.tm_year, "invalid year");
TEST_ASSERT_EQUAL_UINT32_MESSAGE(time_info.tm_wday, actual_time_info.tm_wday, "invalid weekday");
TEST_ASSERT_EQUAL_UINT32_MESSAGE(time_info.tm_yday, actual_time_info.tm_yday, "invalid year day");
}
}
}
}
utest::v1::status_t full_leap_year_case_setup_handler_t(const Case * const source, const size_t index_of_case)
{
rtc_leap_year_support = RTC_FULL_LEAP_YEAR_SUPPORT;
return greentea_case_setup_handler(source, index_of_case);
}
utest::v1::status_t partial_leap_year_case_setup_handler_t(const Case * const source, const size_t index_of_case)
{
rtc_leap_year_support = RTC_4_YEAR_LEAP_YEAR_SUPPORT;
return greentea_case_setup_handler(source, index_of_case);
}
utest::v1::status_t teardown_handler_t(const Case * const source, const size_t passed, const size_t failed,
const failure_t reason)
{
return greentea_case_teardown_handler(source, passed, failed, reason);
}
// Test cases
Case cases[] ={
Case("test make time and local time - RTC leap years full support", full_leap_year_case_setup_handler_t, test_case_mktime_localtime, teardown_handler_t),
Case("test make time and local time - RTC leap years partial support", partial_leap_year_case_setup_handler_t, test_case_mktime_localtime, teardown_handler_t),
};
utest::v1::status_t greentea_test_setup(const size_t number_of_cases)
{
GREENTEA_SETUP(300, "rtc_calc_auto");
return greentea_test_setup_handler(number_of_cases);
}
Specification specification(greentea_test_setup, cases, greentea_test_teardown_handler);
int main()
{
Harness::run(specification);
}

View File

@ -16,14 +16,17 @@
#include "mbed_mktime.h"
/*
* time constants
*/
/* Time constants. */
#define SECONDS_BY_MINUTES 60
#define MINUTES_BY_HOUR 60
#define SECONDS_BY_HOUR (SECONDS_BY_MINUTES * MINUTES_BY_HOUR)
#define HOURS_BY_DAY 24
#define SECONDS_BY_DAY (SECONDS_BY_HOUR * HOURS_BY_DAY)
#define LAST_VALID_YEAR 206
/* Macros which will be used to determine if we are within valid range. */
#define EDGE_TIMESTAMP_FULL_LEAP_YEAR_SUPPORT 3220095 // 7th of February 1970 at 06:28:15
#define EDGE_TIMESTAMP_4_YEAR_LEAP_YEAR_SUPPORT 3133695 // 6th of February 1970 at 06:28:15
/*
* 2 dimensional array containing the number of seconds elapsed before a given
@ -63,10 +66,10 @@ static const uint32_t seconds_before_month[2][12] = {
}
};
bool _rtc_is_leap_year(int year) {
bool _rtc_is_leap_year(int year, rtc_leap_year_support_t leap_year_support) {
/*
* since in practice, the value manipulated by this algorithm lie in the
* range [70 : 138], the algorith can be reduced to: year % 4.
* range: [70 : 206] the algorithm can be reduced to: year % 4 with exception for 200 (year 2100 is not leap year).
* The algorithm valid over the full range of value is:
year = 1900 + year;
@ -80,86 +83,108 @@ bool _rtc_is_leap_year(int year) {
return true;
*/
if (leap_year_support == RTC_FULL_LEAP_YEAR_SUPPORT && year == 200) {
return false; // 2100 is not a leap year
}
return (year) % 4 ? false : true;
}
time_t _rtc_mktime(const struct tm* time) {
// partial check for the upper bound of the range
// normalization might happen at the end of the function
// this solution is faster than checking if the input is after the 19th of
// january 2038 at 03:14:07.
if ((time->tm_year < 70) || (time->tm_year > 138)) {
return ((time_t) -1);
bool _rtc_maketime(const struct tm* time, time_t * seconds, rtc_leap_year_support_t leap_year_support) {
if (seconds == NULL || time == NULL) {
return false;
}
/* Partial check for the upper bound of the range - check years only. Full check will be performed after the
* elapsed time since the beginning of the year is calculated.
*/
if ((time->tm_year < 70) || (time->tm_year > LAST_VALID_YEAR)) {
return false;
}
uint32_t result = time->tm_sec;
result += time->tm_min * SECONDS_BY_MINUTES;
result += time->tm_hour * SECONDS_BY_HOUR;
result += (time->tm_mday - 1) * SECONDS_BY_DAY;
result += seconds_before_month[_rtc_is_leap_year(time->tm_year)][time->tm_mon];
result += seconds_before_month[_rtc_is_leap_year(time->tm_year, leap_year_support)][time->tm_mon];
/* Check if we are within valid range. */
if (time->tm_year == LAST_VALID_YEAR) {
if ((leap_year_support == RTC_FULL_LEAP_YEAR_SUPPORT && result > EDGE_TIMESTAMP_FULL_LEAP_YEAR_SUPPORT) ||
(leap_year_support == RTC_4_YEAR_LEAP_YEAR_SUPPORT && result > EDGE_TIMESTAMP_4_YEAR_LEAP_YEAR_SUPPORT)) {
return false;
}
}
if (time->tm_year > 70) {
// valid in the range [70:138]
/* Valid in the range [70:206]. */
uint32_t count_of_leap_days = ((time->tm_year - 1) / 4) - (70 / 4);
if (leap_year_support == RTC_FULL_LEAP_YEAR_SUPPORT) {
if (time->tm_year > 200) {
count_of_leap_days--; // 2100 is not a leap year
}
}
result += (((time->tm_year - 70) * 365) + count_of_leap_days) * SECONDS_BY_DAY;
}
if (result > INT32_MAX) {
return (time_t) -1;
}
*seconds = result;
return result;
return true;
}
bool _rtc_localtime(time_t timestamp, struct tm* time_info) {
if (((int32_t) timestamp) < 0) {
bool _rtc_localtime(time_t timestamp, struct tm* time_info, rtc_leap_year_support_t leap_year_support) {
if (time_info == NULL) {
return false;
}
}
time_info->tm_sec = timestamp % 60;
timestamp = timestamp / 60; // timestamp in minutes
time_info->tm_min = timestamp % 60;
timestamp = timestamp / 60; // timestamp in hours
time_info->tm_hour = timestamp % 24;
timestamp = timestamp / 24; // timestamp in days;
uint32_t seconds = (uint32_t)timestamp;
// compute the weekday
// The 1st of January 1970 was a Thursday which is equal to 4 in the weekday
// representation ranging from [0:6]
time_info->tm_wday = (timestamp + 4) % 7;
time_info->tm_sec = seconds % 60;
seconds = seconds / 60; // timestamp in minutes
time_info->tm_min = seconds % 60;
seconds = seconds / 60; // timestamp in hours
time_info->tm_hour = seconds % 24;
seconds = seconds / 24; // timestamp in days;
// years start at 70
/* Compute the weekday.
* The 1st of January 1970 was a Thursday which is equal to 4 in the weekday representation ranging from [0:6].
*/
time_info->tm_wday = (seconds + 4) % 7;
/* Years start at 70. */
time_info->tm_year = 70;
while (true) {
if (_rtc_is_leap_year(time_info->tm_year) && timestamp >= 366) {
if (_rtc_is_leap_year(time_info->tm_year, leap_year_support) && seconds >= 366) {
++time_info->tm_year;
timestamp -= 366;
} else if (!_rtc_is_leap_year(time_info->tm_year) && timestamp >= 365) {
seconds -= 366;
} else if (!_rtc_is_leap_year(time_info->tm_year, leap_year_support) && seconds >= 365) {
++time_info->tm_year;
timestamp -= 365;
seconds -= 365;
} else {
// the remaining days are less than a years
/* The remaining days are less than a years. */
break;
}
}
time_info->tm_yday = timestamp;
time_info->tm_yday = seconds;
// convert days into seconds and find the current month
timestamp *= SECONDS_BY_DAY;
/* Convert days into seconds and find the current month. */
seconds *= SECONDS_BY_DAY;
time_info->tm_mon = 11;
bool leap = _rtc_is_leap_year(time_info->tm_year);
bool leap = _rtc_is_leap_year(time_info->tm_year, leap_year_support);
for (uint32_t i = 0; i < 12; ++i) {
if ((uint32_t) timestamp < seconds_before_month[leap][i]) {
if ((uint32_t) seconds < seconds_before_month[leap][i]) {
time_info->tm_mon = i - 1;
break;
}
}
// remove month from timestamp and compute the number of days.
// note: unlike other fields, days are not 0 indexed.
timestamp -= seconds_before_month[leap][time_info->tm_mon];
time_info->tm_mday = (timestamp / SECONDS_BY_DAY) + 1;
/* Remove month from timestamp and compute the number of days.
* Note: unlike other fields, days are not 0 indexed.
*/
seconds -= seconds_before_month[leap][time_info->tm_mon];
time_info->tm_mday = (seconds / SECONDS_BY_DAY) + 1;
return true;
}

View File

@ -33,14 +33,34 @@ extern "C" {
* @{
*/
/* Time range across the whole 32-bit range should be supported which means that years in range 1970 - 2106 can be
* encoded. We have two types of RTC devices:
* a) RTCs which handles all leap years in the mentioned year range correctly. Leap year is determined by checking if
* the year counter value is divisible by 400, 100, and 4. No problem here.
* b) RTCs which handles leap years correctly up to 2100. The RTC does a simple bit comparison to see if the two
* lowest order bits of the year counter are zero. In this case 2100 year will be considered
* incorrectly as a leap year, so the last valid point in time will be 28.02.2100 23:59:59 and next day will be
* 29.02.2100 (invalid). So after 28.02.2100 the day counter will be off by a day.
*/
typedef enum {
RTC_FULL_LEAP_YEAR_SUPPORT,
RTC_4_YEAR_LEAP_YEAR_SUPPORT
} rtc_leap_year_support_t;
/** Compute if a year is a leap year or not.
*
* @param year The year to test it shall be in the range [70:138]. Year 0 is
* @param year The year to test it shall be in the range [70:206]. Year 0 is
* translated into year 1900 CE.
* @param leap_year_support use RTC_FULL_LEAP_YEAR_SUPPORT if RTC device is able
* to correctly detect all leap years in range [70:206] otherwise use RTC_4_YEAR_LEAP_YEAR_SUPPORT.
*
* @return true if the year in input is a leap year and false otherwise.
* @note - For use by the HAL only
*
* @note For use by the HAL only
* @note Year 2100 is treated differently for devices with full leap year support and devices with
* partial leap year support. Devices with partial leap year support treats 2100 as a leap year.
*/
bool _rtc_is_leap_year(int year);
bool _rtc_is_leap_year(int year, rtc_leap_year_support_t leap_year_support);
/* Convert a calendar time into time since UNIX epoch as a time_t.
*
@ -48,7 +68,7 @@ bool _rtc_is_leap_year(int year);
* tailored around RTC peripherals needs and is not by any mean a complete
* replacement of mktime.
*
* @param calendar_time The calendar time to convert into a time_t since epoch.
* @param time The calendar time to convert into a time_t since epoch.
* The fields from tm used for the computation are:
* - tm_sec
* - tm_min
@ -57,17 +77,20 @@ bool _rtc_is_leap_year(int year);
* - tm_mon
* - tm_year
* Other fields are ignored and won't be renormalized by a call to this function.
* A valid calendar time is comprised between the 1st january of 1970 at
* 00:00:00 and the 19th of january 2038 at 03:14:07.
* A valid calendar time is comprised between:
* the 1st of January 1970 at 00:00:00 to the 7th of February 2106 at 06:28:15.
* @param leap_year_support use RTC_FULL_LEAP_YEAR_SUPPORT if RTC device is able
* to correctly detect all leap years in range [70:206] otherwise use RTC_4_YEAR_LEAP_YEAR_SUPPORT.
* @param seconds holder for the result - calendar time as seconds since UNIX epoch.
*
* @return The calendar time as seconds since UNIX epoch if the input is in the
* valid range. Otherwise ((time_t) -1).
* @return true on success, false if conversion error occurred.
*
* @note Leap seconds are not supported.
* @note Values in output range from 0 to INT_MAX.
* @note - For use by the HAL only
* @note Values in output range from 0 to UINT_MAX.
* @note Full and partial leap years support.
* @note For use by the HAL only
*/
time_t _rtc_mktime(const struct tm* calendar_time);
bool _rtc_maketime(const struct tm* time, time_t * seconds, rtc_leap_year_support_t leap_year_support);
/* Convert a given time in seconds since epoch into calendar time.
*
@ -76,7 +99,7 @@ time_t _rtc_mktime(const struct tm* calendar_time);
* complete of localtime.
*
* @param timestamp The time (in seconds) to convert into calendar time. Valid
* input are in the range [0 : INT32_MAX].
* input are in the range [0 : UINT32_MAX].
* @param calendar_time Pointer to the object which will contain the result of
* the conversion. The tm fields filled by this function are:
* - tm_sec
@ -88,11 +111,14 @@ time_t _rtc_mktime(const struct tm* calendar_time);
* - tm_wday
* - tm_yday
* The object remains untouched if the time in input is invalid.
* @param leap_year_support use RTC_FULL_LEAP_YEAR_SUPPORT if RTC device is able
* to correctly detect all leap years in range [70:206] otherwise use RTC_4_YEAR_LEAP_YEAR_SUPPORT.
* @return true if the conversion was successful, false otherwise.
*
* @note - For use by the HAL only
* @note For use by the HAL only.
* @note Full and partial leap years support.
*/
bool _rtc_localtime(time_t timestamp, struct tm* calendar_time);
bool _rtc_localtime(time_t timestamp, struct tm* time_info, rtc_leap_year_support_t leap_year_support);
/** @}*/

View File

@ -71,7 +71,11 @@ time_t rtc_read(void)
timeinfo.tm_year = (ul_year - 1900);
/* Convert to timestamp */
time_t t = _rtc_mktime(&timeinfo);
time_t t;
if (_rtc_maketime(&timeinfo, &t, RTC_4_YEAR_LEAP_YEAR_SUPPORT) == false) {
return 0;
}
return t;
}
@ -81,8 +85,9 @@ void rtc_write(time_t t)
/* Initialize the RTC is not yet initialized */
rtc_init();
}
struct tm timeinfo;
if (_rtc_localtime(t, &timeinfo) == false) {
if (_rtc_localtime(t, &timeinfo, RTC_4_YEAR_LEAP_YEAR_SUPPORT) == false) {
return;
}
uint32_t ul_hour, ul_minute, ul_second;

View File

@ -94,7 +94,10 @@ time_t rtc_read(void)
timeinfo.tm_sec = rtc_datetime.u32Second;
// Convert to timestamp
time_t t = _rtc_mktime(&timeinfo);
time_t t;
if (_rtc_maketime(&timeinfo, &t, RTC_FULL_LEAP_YEAR_SUPPORT) == false) {
return 0;
}
return t;
}
@ -104,10 +107,10 @@ void rtc_write(time_t t)
if (! rtc_isenabled()) {
rtc_init();
}
// Convert timestamp to struct tm
struct tm timeinfo;
if (_rtc_localtime(t, &timeinfo) == false) {
if (_rtc_localtime(t, &timeinfo, RTC_FULL_LEAP_YEAR_SUPPORT) == false) {
return;
}

View File

@ -96,7 +96,10 @@ time_t rtc_read(void)
timeinfo.tm_sec = rtc_datetime.u32Second;
// Convert to timestamp
time_t t = _rtc_mktime(&timeinfo);
time_t t;
if (_rtc_maketime(&timeinfo, &t, RTC_FULL_LEAP_YEAR_SUPPORT) == false) {
return 0;
}
return t;
}
@ -109,7 +112,7 @@ void rtc_write(time_t t)
// Convert timestamp to struct tm
struct tm timeinfo;
if (_rtc_localtime(t, &timeinfo) == false) {
if (_rtc_localtime(t, &timeinfo, RTC_FULL_LEAP_YEAR_SUPPORT) == false) {
return;
}

View File

@ -94,7 +94,10 @@ time_t rtc_read(void)
timeinfo.tm_sec = rtc_datetime.u32Second;
// Convert to timestamp
time_t t = _rtc_mktime(&timeinfo);
time_t t;
if (_rtc_maketime(&timeinfo, &t, RTC_FULL_LEAP_YEAR_SUPPORT) == false) {
return 0;
}
return t;
}
@ -104,10 +107,10 @@ void rtc_write(time_t t)
if (! rtc_isenabled()) {
rtc_init();
}
// Convert timestamp to struct tm
struct tm timeinfo;
if (_rtc_localtime(t, &timeinfo) == false) {
if (_rtc_localtime(t, &timeinfo, RTC_FULL_LEAP_YEAR_SUPPORT) == false) {
return;
}

View File

@ -94,7 +94,10 @@ time_t rtc_read(void)
timeinfo.tm_sec = rtc_datetime.u32Second;
// Convert to timestamp
time_t t = _rtc_mktime(&timeinfo);
time_t t;
if (_rtc_maketime(&timeinfo, &t, RTC_FULL_LEAP_YEAR_SUPPORT) == false) {
return 0;
}
return t;
}
@ -104,10 +107,10 @@ void rtc_write(time_t t)
if (! rtc_isenabled()) {
rtc_init();
}
// Convert timestamp to struct tm
struct tm timeinfo;
if (_rtc_localtime(t, &timeinfo) == false) {
if (_rtc_localtime(t, &timeinfo, RTC_FULL_LEAP_YEAR_SUPPORT) == false) {
return;
}

View File

@ -89,7 +89,10 @@ time_t rtc_read(void) {
timeinfo.tm_year = LPC_RTC->YEAR - 1900;
// Convert to timestamp
time_t t = _rtc_mktime(&timeinfo);
time_t t;
if (_rtc_maketime(&timeinfo, &t, RTC_4_YEAR_LEAP_YEAR_SUPPORT) == false) {
return 0;
}
return t;
}
@ -97,10 +100,10 @@ time_t rtc_read(void) {
void rtc_write(time_t t) {
// Convert the time in to a tm
struct tm timeinfo;
if (_rtc_localtime(t, &timeinfo) == false) {
if (_rtc_localtime(t, &timeinfo, RTC_4_YEAR_LEAP_YEAR_SUPPORT) == false) {
return;
}
// Pause clock, and clear counter register (clears us count)
LPC_RTC->CCR |= 2;

View File

@ -88,7 +88,10 @@ time_t rtc_read(void) {
timeinfo.tm_year = LPC_RTC->YEAR - 1900;
// Convert to timestamp
time_t t = _rtc_mktime(&timeinfo);
time_t t;
if (_rtc_maketime(&timeinfo, &t, RTC_4_YEAR_LEAP_YEAR_SUPPORT) == false) {
return 0;
}
return t;
}
@ -96,10 +99,10 @@ time_t rtc_read(void) {
void rtc_write(time_t t) {
// Convert the time in to a tm
struct tm timeinfo;
if (_rtc_localtime(t, &timeinfo) == false) {
if (_rtc_localtime(t, &timeinfo, RTC_4_YEAR_LEAP_YEAR_SUPPORT) == false) {
return;
}
// Pause clock, and clear counter register (clears us count)
LPC_RTC->CCR |= 2;

View File

@ -102,7 +102,10 @@ time_t rtc_read(void) {
timeinfo.tm_year = LPC_RTC->TIME[RTC_TIMETYPE_YEAR] - 1900;
// Convert to timestamp
time_t t = _rtc_mktime(&timeinfo);
time_t t;
if (_rtc_maketime(&timeinfo, &t, RTC_4_YEAR_LEAP_YEAR_SUPPORT) == false) {
return 0;
}
return t;
}
@ -110,10 +113,10 @@ time_t rtc_read(void) {
void rtc_write(time_t t) {
// Convert the time in to a tm
struct tm timeinfo;
if (_rtc_localtime(t, &timeinfo) == false) {
if (_rtc_localtime(t, &timeinfo, RTC_4_YEAR_LEAP_YEAR_SUPPORT) == false) {
return;
}
// Pause clock, and clear counter register (clears us count)
LPC_RTC->CCR |= 2;

View File

@ -308,6 +308,7 @@ void rtc_write(time_t t) {
if (_rtc_localtime(t, &timeinfo) == false) {
return;
}
volatile uint16_t dummy_read;
if (rtc_isenabled() != 0) {

View File

@ -70,7 +70,7 @@
#define SHIFT_1BYTE (8u)
#define SHIFT_2BYTE (16u)
#define TIME_ERROR_VAL (0xFFFFFFFFu)
#define TIME_ERROR_VAL (0u)
static int rtc_dec8_to_hex(uint8_t dec_val, uint8_t offset, int *hex_val);
static int rtc_dec16_to_hex(uint16_t dec_val, uint16_t offset, int *hex_val);
@ -248,7 +248,9 @@ time_t rtc_read(void) {
if (err == 0) {
// Convert to timestamp
t = _rtc_mktime(&timeinfo);
if (_rtc_maketime(&timeinfo, &t, RTC_FULL_LEAP_YEAR_SUPPORT) == false) {
return TIME_ERROR_VAL;
}
} else {
// Error
t = TIME_ERROR_VAL;
@ -339,9 +341,10 @@ static int rtc_dec16_to_hex(uint16_t dec_val, uint16_t offset, int *hex_val) {
void rtc_write(time_t t) {
struct tm timeinfo;
if (_rtc_localtime(t, &timeinfo) == false) {
if (_rtc_localtime(t, &timeinfo, RTC_FULL_LEAP_YEAR_SUPPORT) == false) {
return;
}
volatile uint16_t dummy_read;
if (rtc_isenabled() != 0) {

View File

@ -231,7 +231,10 @@ time_t rtc_read(void)
timeinfo.tm_isdst = -1;
// Convert to timestamp
time_t t = _rtc_mktime(&timeinfo);
time_t t;
if (_rtc_maketime(&timeinfo, &t, RTC_4_YEAR_LEAP_YEAR_SUPPORT) == false) {
return 0;
}
return t;
}
@ -245,7 +248,7 @@ void rtc_write(time_t t)
// Convert the time into a tm
struct tm timeinfo;
if (_rtc_localtime(t, &timeinfo) == false) {
if (_rtc_localtime(t, &timeinfo, RTC_4_YEAR_LEAP_YEAR_SUPPORT) == false) {
return;
}