Skip to content

Commit dc5a66d

Browse files
committed
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.
1 parent 1394bf9 commit dc5a66d

File tree

2 files changed

+111
-60
lines changed

2 files changed

+111
-60
lines changed

platform/mbed_mktime.c

Lines changed: 71 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,17 @@
1616

1717
#include "mbed_mktime.h"
1818

19-
/*
20-
* time constants
21-
*/
19+
/* Time constants. */
2220
#define SECONDS_BY_MINUTES 60
2321
#define MINUTES_BY_HOUR 60
2422
#define SECONDS_BY_HOUR (SECONDS_BY_MINUTES * MINUTES_BY_HOUR)
2523
#define HOURS_BY_DAY 24
2624
#define SECONDS_BY_DAY (SECONDS_BY_HOUR * HOURS_BY_DAY)
25+
#define LAST_VALID_YEAR 206
26+
27+
/* Macros which will be used to determine if we are within valid range. */
28+
#define EDGE_TIMESTAMP_FULL_LEAP_YEAR_SUPPORT 3220095 // 7th of February 1970 at 06:28:15
29+
#define EDGE_TIMESTAMP_4_YEAR_LEAP_YEAR_SUPPORT 3133695 // 6th of February 1970 at 06:28:15
2730

2831
/*
2932
* 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] = {
6366
}
6467
};
6568

66-
bool _rtc_is_leap_year(int year) {
69+
bool _rtc_is_leap_year(int year, rtc_leap_year_support_t leap_year_support) {
6770
/*
6871
* since in practice, the value manipulated by this algorithm lie in the
69-
* range [70 : 138], the algorith can be reduced to: year % 4.
72+
* range: [70 : 206] the algorithm can be reduced to: year % 4 with exception for 200 (year 2100 is not leap year).
7073
* The algorithm valid over the full range of value is:
7174
7275
year = 1900 + year;
@@ -80,86 +83,108 @@ bool _rtc_is_leap_year(int year) {
8083
return true;
8184
8285
*/
86+
if (leap_year_support == RTC_FULL_LEAP_YEAR_SUPPORT && year == 200) {
87+
return false; // 2100 is not a leap year
88+
}
89+
8390
return (year) % 4 ? false : true;
8491
}
8592

86-
time_t _rtc_mktime(const struct tm* time) {
87-
// partial check for the upper bound of the range
88-
// normalization might happen at the end of the function
89-
// this solution is faster than checking if the input is after the 19th of
90-
// january 2038 at 03:14:07.
91-
if ((time->tm_year < 70) || (time->tm_year > 138)) {
92-
return ((time_t) -1);
93+
bool _rtc_maketime(const struct tm* time, time_t * seconds, rtc_leap_year_support_t leap_year_support) {
94+
if (seconds == NULL || time == NULL) {
95+
return false;
96+
}
97+
98+
/* Partial check for the upper bound of the range - check years only. Full check will be performed after the
99+
* elapsed time since the beginning of the year is calculated.
100+
*/
101+
if ((time->tm_year < 70) || (time->tm_year > LAST_VALID_YEAR)) {
102+
return false;
93103
}
94104

95105
uint32_t result = time->tm_sec;
96106
result += time->tm_min * SECONDS_BY_MINUTES;
97107
result += time->tm_hour * SECONDS_BY_HOUR;
98108
result += (time->tm_mday - 1) * SECONDS_BY_DAY;
99-
result += seconds_before_month[_rtc_is_leap_year(time->tm_year)][time->tm_mon];
109+
result += seconds_before_month[_rtc_is_leap_year(time->tm_year, leap_year_support)][time->tm_mon];
110+
111+
/* Check if we are within valid range. */
112+
if (time->tm_year == LAST_VALID_YEAR) {
113+
if ((leap_year_support == RTC_FULL_LEAP_YEAR_SUPPORT && result > EDGE_TIMESTAMP_FULL_LEAP_YEAR_SUPPORT) ||
114+
(leap_year_support == RTC_4_YEAR_LEAP_YEAR_SUPPORT && result > EDGE_TIMESTAMP_4_YEAR_LEAP_YEAR_SUPPORT)) {
115+
return false;
116+
}
117+
}
100118

101119
if (time->tm_year > 70) {
102-
// valid in the range [70:138]
120+
/* Valid in the range [70:206]. */
103121
uint32_t count_of_leap_days = ((time->tm_year - 1) / 4) - (70 / 4);
122+
if (leap_year_support == RTC_FULL_LEAP_YEAR_SUPPORT) {
123+
if (time->tm_year > 200) {
124+
count_of_leap_days--; // 2100 is not a leap year
125+
}
126+
}
127+
104128
result += (((time->tm_year - 70) * 365) + count_of_leap_days) * SECONDS_BY_DAY;
105129
}
106130

107-
if (result > INT32_MAX) {
108-
return (time_t) -1;
109-
}
131+
*seconds = result;
110132

111-
return result;
133+
return true;
112134
}
113135

114-
bool _rtc_localtime(time_t timestamp, struct tm* time_info) {
115-
if (((int32_t) timestamp) < 0) {
136+
bool _rtc_localtime(time_t timestamp, struct tm* time_info, rtc_leap_year_support_t leap_year_support) {
137+
if (time_info == NULL) {
116138
return false;
117-
}
139+
}
140+
141+
uint32_t seconds = (uint32_t)timestamp;
118142

119-
time_info->tm_sec = timestamp % 60;
120-
timestamp = timestamp / 60; // timestamp in minutes
121-
time_info->tm_min = timestamp % 60;
122-
timestamp = timestamp / 60; // timestamp in hours
123-
time_info->tm_hour = timestamp % 24;
124-
timestamp = timestamp / 24; // timestamp in days;
143+
time_info->tm_sec = seconds % 60;
144+
seconds = seconds / 60; // timestamp in minutes
145+
time_info->tm_min = seconds % 60;
146+
seconds = seconds / 60; // timestamp in hours
147+
time_info->tm_hour = seconds % 24;
148+
seconds = seconds / 24; // timestamp in days;
125149

126-
// compute the weekday
127-
// The 1st of January 1970 was a Thursday which is equal to 4 in the weekday
128-
// representation ranging from [0:6]
129-
time_info->tm_wday = (timestamp + 4) % 7;
150+
/* Compute the weekday.
151+
* The 1st of January 1970 was a Thursday which is equal to 4 in the weekday representation ranging from [0:6].
152+
*/
153+
time_info->tm_wday = (seconds + 4) % 7;
130154

131-
// years start at 70
155+
/* Years start at 70. */
132156
time_info->tm_year = 70;
133157
while (true) {
134-
if (_rtc_is_leap_year(time_info->tm_year) && timestamp >= 366) {
158+
if (_rtc_is_leap_year(time_info->tm_year, leap_year_support) && seconds >= 366) {
135159
++time_info->tm_year;
136-
timestamp -= 366;
137-
} else if (!_rtc_is_leap_year(time_info->tm_year) && timestamp >= 365) {
160+
seconds -= 366;
161+
} else if (!_rtc_is_leap_year(time_info->tm_year, leap_year_support) && seconds >= 365) {
138162
++time_info->tm_year;
139-
timestamp -= 365;
163+
seconds -= 365;
140164
} else {
141-
// the remaining days are less than a years
165+
/* The remaining days are less than a years. */
142166
break;
143167
}
144168
}
145169

146-
time_info->tm_yday = timestamp;
170+
time_info->tm_yday = seconds;
147171

148-
// convert days into seconds and find the current month
149-
timestamp *= SECONDS_BY_DAY;
172+
/* Convert days into seconds and find the current month. */
173+
seconds *= SECONDS_BY_DAY;
150174
time_info->tm_mon = 11;
151-
bool leap = _rtc_is_leap_year(time_info->tm_year);
175+
bool leap = _rtc_is_leap_year(time_info->tm_year, leap_year_support);
152176
for (uint32_t i = 0; i < 12; ++i) {
153-
if ((uint32_t) timestamp < seconds_before_month[leap][i]) {
177+
if ((uint32_t) seconds < seconds_before_month[leap][i]) {
154178
time_info->tm_mon = i - 1;
155179
break;
156180
}
157181
}
158182

159-
// remove month from timestamp and compute the number of days.
160-
// note: unlike other fields, days are not 0 indexed.
161-
timestamp -= seconds_before_month[leap][time_info->tm_mon];
162-
time_info->tm_mday = (timestamp / SECONDS_BY_DAY) + 1;
183+
/* Remove month from timestamp and compute the number of days.
184+
* Note: unlike other fields, days are not 0 indexed.
185+
*/
186+
seconds -= seconds_before_month[leap][time_info->tm_mon];
187+
time_info->tm_mday = (seconds / SECONDS_BY_DAY) + 1;
163188

164189
return true;
165190
}

platform/mbed_mktime.h

Lines changed: 40 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -33,22 +33,42 @@ extern "C" {
3333
* @{
3434
*/
3535

36+
/* Time range across the whole 32-bit range should be supported which means that years in range 1970 - 2106 can be
37+
* encoded. We have two types of RTC devices:
38+
* a) RTCs which handles all leap years in the mentioned year range correctly. Leap year is determined by checking if
39+
* the year counter value is divisible by 400, 100, and 4. No problem here.
40+
* b) RTCs which handles leap years correctly up to 2100. The RTC does a simple bit comparison to see if the two
41+
* lowest order bits of the year counter are zero. In this case 2100 year will be considered
42+
* 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
43+
* 29.02.2100 (invalid). So after 28.02.2100 the day counter will be off by a day.
44+
*/
45+
typedef enum {
46+
RTC_FULL_LEAP_YEAR_SUPPORT,
47+
RTC_4_YEAR_LEAP_YEAR_SUPPORT
48+
} rtc_leap_year_support_t;
49+
3650
/** Compute if a year is a leap year or not.
3751
*
38-
* @param year The year to test it shall be in the range [70:138]. Year 0 is
52+
* @param year The year to test it shall be in the range [70:206]. Year 0 is
3953
* translated into year 1900 CE.
54+
* @param leap_year_support use RTC_FULL_LEAP_YEAR_SUPPORT if RTC device is able
55+
* to correctly detect all leap years in range [70:206] otherwise use RTC_4_YEAR_LEAP_YEAR_SUPPORT.
56+
*
4057
* @return true if the year in input is a leap year and false otherwise.
41-
* @note - For use by the HAL only
58+
*
59+
* @note For use by the HAL only
60+
* @note Year 2100 is treated differently for devices with full leap year support and devices with
61+
* partial leap year support. Devices with partial leap year support treats 2100 as a leap year.
4262
*/
43-
bool _rtc_is_leap_year(int year);
63+
bool _rtc_is_leap_year(int year, rtc_leap_year_support_t leap_year_support);
4464

4565
/* Convert a calendar time into time since UNIX epoch as a time_t.
4666
*
4767
* This function is a thread safe (partial) replacement for mktime. It is
4868
* tailored around RTC peripherals needs and is not by any mean a complete
4969
* replacement of mktime.
5070
*
51-
* @param calendar_time The calendar time to convert into a time_t since epoch.
71+
* @param time The calendar time to convert into a time_t since epoch.
5272
* The fields from tm used for the computation are:
5373
* - tm_sec
5474
* - tm_min
@@ -57,17 +77,20 @@ bool _rtc_is_leap_year(int year);
5777
* - tm_mon
5878
* - tm_year
5979
* Other fields are ignored and won't be renormalized by a call to this function.
60-
* A valid calendar time is comprised between the 1st january of 1970 at
61-
* 00:00:00 and the 19th of january 2038 at 03:14:07.
80+
* A valid calendar time is comprised between:
81+
* the 1st of January 1970 at 00:00:00 to the 7th of February 2106 at 06:28:15.
82+
* @param leap_year_support use RTC_FULL_LEAP_YEAR_SUPPORT if RTC device is able
83+
* to correctly detect all leap years in range [70:206] otherwise use RTC_4_YEAR_LEAP_YEAR_SUPPORT.
84+
* @param seconds holder for the result - calendar time as seconds since UNIX epoch.
6285
*
63-
* @return The calendar time as seconds since UNIX epoch if the input is in the
64-
* valid range. Otherwise ((time_t) -1).
86+
* @return true on success, false if conversion error occurred.
6587
*
6688
* @note Leap seconds are not supported.
67-
* @note Values in output range from 0 to INT_MAX.
68-
* @note - For use by the HAL only
89+
* @note Values in output range from 0 to UINT_MAX.
90+
* @note Full and partial leap years support.
91+
* @note For use by the HAL only
6992
*/
70-
time_t _rtc_mktime(const struct tm* calendar_time);
93+
bool _rtc_maketime(const struct tm* time, time_t * seconds, rtc_leap_year_support_t leap_year_support);
7194

7295
/* Convert a given time in seconds since epoch into calendar time.
7396
*
@@ -76,7 +99,7 @@ time_t _rtc_mktime(const struct tm* calendar_time);
7699
* complete of localtime.
77100
*
78101
* @param timestamp The time (in seconds) to convert into calendar time. Valid
79-
* input are in the range [0 : INT32_MAX].
102+
* input are in the range [0 : UINT32_MAX].
80103
* @param calendar_time Pointer to the object which will contain the result of
81104
* the conversion. The tm fields filled by this function are:
82105
* - tm_sec
@@ -88,11 +111,14 @@ time_t _rtc_mktime(const struct tm* calendar_time);
88111
* - tm_wday
89112
* - tm_yday
90113
* The object remains untouched if the time in input is invalid.
114+
* @param leap_year_support use RTC_FULL_LEAP_YEAR_SUPPORT if RTC device is able
115+
* to correctly detect all leap years in range [70:206] otherwise use RTC_4_YEAR_LEAP_YEAR_SUPPORT.
91116
* @return true if the conversion was successful, false otherwise.
92117
*
93-
* @note - For use by the HAL only
118+
* @note For use by the HAL only.
119+
* @note Full and partial leap years support.
94120
*/
95-
bool _rtc_localtime(time_t timestamp, struct tm* calendar_time);
121+
bool _rtc_localtime(time_t timestamp, struct tm* time_info, rtc_leap_year_support_t leap_year_support);
96122

97123
/** @}*/
98124

0 commit comments

Comments
 (0)