mirror of https://github.com/ARMmbed/mbed-os.git
Add support for extended RTC.
Provide support to use whole 32-bit range (unsigned int) to hold time since UNIX epoch. The suppoerted time range is now from the 1st of January 1970 at 00:00:00 to the 7th of February 2106 at 06:28:15. Add support for two types of RTC devices: - 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. - 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.pull/5363/head
parent
1394bf95f1
commit
dc5a66dc5c
|
@ -16,14 +16,17 @@
|
||||||
|
|
||||||
#include "mbed_mktime.h"
|
#include "mbed_mktime.h"
|
||||||
|
|
||||||
/*
|
/* Time constants. */
|
||||||
* time constants
|
|
||||||
*/
|
|
||||||
#define SECONDS_BY_MINUTES 60
|
#define SECONDS_BY_MINUTES 60
|
||||||
#define MINUTES_BY_HOUR 60
|
#define MINUTES_BY_HOUR 60
|
||||||
#define SECONDS_BY_HOUR (SECONDS_BY_MINUTES * MINUTES_BY_HOUR)
|
#define SECONDS_BY_HOUR (SECONDS_BY_MINUTES * MINUTES_BY_HOUR)
|
||||||
#define HOURS_BY_DAY 24
|
#define HOURS_BY_DAY 24
|
||||||
#define SECONDS_BY_DAY (SECONDS_BY_HOUR * HOURS_BY_DAY)
|
#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
|
* 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
|
* 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:
|
* The algorithm valid over the full range of value is:
|
||||||
|
|
||||||
year = 1900 + year;
|
year = 1900 + year;
|
||||||
|
@ -80,86 +83,108 @@ bool _rtc_is_leap_year(int year) {
|
||||||
return true;
|
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;
|
return (year) % 4 ? false : true;
|
||||||
}
|
}
|
||||||
|
|
||||||
time_t _rtc_mktime(const struct tm* time) {
|
bool _rtc_maketime(const struct tm* time, time_t * seconds, rtc_leap_year_support_t leap_year_support) {
|
||||||
// partial check for the upper bound of the range
|
if (seconds == NULL || time == NULL) {
|
||||||
// normalization might happen at the end of the function
|
return false;
|
||||||
// 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)) {
|
/* Partial check for the upper bound of the range - check years only. Full check will be performed after the
|
||||||
return ((time_t) -1);
|
* 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;
|
uint32_t result = time->tm_sec;
|
||||||
result += time->tm_min * SECONDS_BY_MINUTES;
|
result += time->tm_min * SECONDS_BY_MINUTES;
|
||||||
result += time->tm_hour * SECONDS_BY_HOUR;
|
result += time->tm_hour * SECONDS_BY_HOUR;
|
||||||
result += (time->tm_mday - 1) * SECONDS_BY_DAY;
|
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) {
|
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);
|
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;
|
result += (((time->tm_year - 70) * 365) + count_of_leap_days) * SECONDS_BY_DAY;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result > INT32_MAX) {
|
*seconds = result;
|
||||||
return (time_t) -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool _rtc_localtime(time_t timestamp, struct tm* time_info) {
|
bool _rtc_localtime(time_t timestamp, struct tm* time_info, rtc_leap_year_support_t leap_year_support) {
|
||||||
if (((int32_t) timestamp) < 0) {
|
if (time_info == NULL) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
time_info->tm_sec = timestamp % 60;
|
uint32_t seconds = (uint32_t)timestamp;
|
||||||
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;
|
|
||||||
|
|
||||||
// compute the weekday
|
time_info->tm_sec = seconds % 60;
|
||||||
// The 1st of January 1970 was a Thursday which is equal to 4 in the weekday
|
seconds = seconds / 60; // timestamp in minutes
|
||||||
// representation ranging from [0:6]
|
time_info->tm_min = seconds % 60;
|
||||||
time_info->tm_wday = (timestamp + 4) % 7;
|
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;
|
time_info->tm_year = 70;
|
||||||
while (true) {
|
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;
|
++time_info->tm_year;
|
||||||
timestamp -= 366;
|
seconds -= 366;
|
||||||
} else if (!_rtc_is_leap_year(time_info->tm_year) && timestamp >= 365) {
|
} else if (!_rtc_is_leap_year(time_info->tm_year, leap_year_support) && seconds >= 365) {
|
||||||
++time_info->tm_year;
|
++time_info->tm_year;
|
||||||
timestamp -= 365;
|
seconds -= 365;
|
||||||
} else {
|
} else {
|
||||||
// the remaining days are less than a years
|
/* The remaining days are less than a years. */
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
time_info->tm_yday = timestamp;
|
time_info->tm_yday = seconds;
|
||||||
|
|
||||||
// convert days into seconds and find the current month
|
/* Convert days into seconds and find the current month. */
|
||||||
timestamp *= SECONDS_BY_DAY;
|
seconds *= SECONDS_BY_DAY;
|
||||||
time_info->tm_mon = 11;
|
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) {
|
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;
|
time_info->tm_mon = i - 1;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove month from timestamp and compute the number of days.
|
/* Remove month from timestamp and compute the number of days.
|
||||||
// note: unlike other fields, days are not 0 indexed.
|
* 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;
|
seconds -= seconds_before_month[leap][time_info->tm_mon];
|
||||||
|
time_info->tm_mday = (seconds / SECONDS_BY_DAY) + 1;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.
|
/** 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.
|
* 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.
|
* @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.
|
/* 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
|
* tailored around RTC peripherals needs and is not by any mean a complete
|
||||||
* replacement of mktime.
|
* 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:
|
* The fields from tm used for the computation are:
|
||||||
* - tm_sec
|
* - tm_sec
|
||||||
* - tm_min
|
* - tm_min
|
||||||
|
@ -57,17 +77,20 @@ bool _rtc_is_leap_year(int year);
|
||||||
* - tm_mon
|
* - tm_mon
|
||||||
* - tm_year
|
* - tm_year
|
||||||
* Other fields are ignored and won't be renormalized by a call to this function.
|
* 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
|
* A valid calendar time is comprised between:
|
||||||
* 00:00:00 and the 19th of january 2038 at 03:14:07.
|
* 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
|
* @return true on success, false if conversion error occurred.
|
||||||
* valid range. Otherwise ((time_t) -1).
|
|
||||||
*
|
*
|
||||||
* @note Leap seconds are not supported.
|
* @note Leap seconds are not supported.
|
||||||
* @note Values in output range from 0 to INT_MAX.
|
* @note Values in output range from 0 to UINT_MAX.
|
||||||
* @note - For use by the HAL only
|
* @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.
|
/* 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.
|
* complete of localtime.
|
||||||
*
|
*
|
||||||
* @param timestamp The time (in seconds) to convert into calendar time. Valid
|
* @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
|
* @param calendar_time Pointer to the object which will contain the result of
|
||||||
* the conversion. The tm fields filled by this function are:
|
* the conversion. The tm fields filled by this function are:
|
||||||
* - tm_sec
|
* - tm_sec
|
||||||
|
@ -88,11 +111,14 @@ time_t _rtc_mktime(const struct tm* calendar_time);
|
||||||
* - tm_wday
|
* - tm_wday
|
||||||
* - tm_yday
|
* - tm_yday
|
||||||
* The object remains untouched if the time in input is invalid.
|
* 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.
|
* @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);
|
||||||
|
|
||||||
/** @}*/
|
/** @}*/
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue