2 * This file is part of Cleanflight, Betaflight and INAV.
4 * This Source Code Form is subject to the terms of the Mozilla Public
5 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
6 * You can obtain one at http://mozilla.org/MPL/2.0/.
8 * Alternatively, the contents of this file may be used under the terms
9 * of the GNU General Public License Version 3, as described below:
11 * This file is free software: you may copy, redistribute and/or modify
12 * it under the terms of the GNU General Public License as published by the
13 * Free Software Foundation, either version 3 of the License, or (at your
14 * option) any later version.
16 * This file is distributed in the hope that it will be useful, but
17 * WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
19 * Public License for more details.
21 * You should have received a copy of the GNU General Public License
22 * along with this program. If not, see http://www.gnu.org/licenses/.
24 * @author Alberto Garcia Hierro <alberto@garciahierro.com>
32 #include "common/maths.h"
33 #include "common/printf.h"
34 #include "common/time.h"
36 #include "drivers/persistent.h"
38 #include "pg/pg_ids.h"
40 #include "drivers/time.h"
44 // For the "modulo 4" arithmetic to work, we need a leap base year
45 #define REFERENCE_YEAR 2000
46 // Offset (seconds) from the UNIX epoch (1970-01-01) to 2000-01-01
47 #define EPOCH_2000_OFFSET 946684800
49 #define MILLIS_PER_SECOND 1000
51 // rtcTime_t when the system was started.
52 // Calculated in rtcSet().
53 static rtcTime_t started
= 0;
55 static const uint16_t days
[4][12] =
57 { 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335},
58 { 366, 397, 425, 456, 486, 517, 547, 578, 609, 639, 670, 700},
59 { 731, 762, 790, 821, 851, 882, 912, 943, 974, 1004, 1035, 1065},
60 {1096, 1127, 1155, 1186, 1216, 1247, 1277, 1308, 1339, 1369, 1400, 1430},
63 PG_REGISTER_WITH_RESET_TEMPLATE(timeConfig_t
, timeConfig
, PG_TIME_CONFIG
, 0);
65 PG_RESET_TEMPLATE(timeConfig_t
, timeConfig
,
66 .tz_offsetMinutes
= 0,
69 static rtcTime_t
dateTimeToRtcTime(dateTime_t
*dt
)
71 unsigned int second
= dt
->seconds
; // 0-59
72 unsigned int minute
= dt
->minutes
; // 0-59
73 unsigned int hour
= dt
->hours
; // 0-23
74 unsigned int day
= dt
->day
- 1; // 0-30
75 unsigned int month
= dt
->month
- 1; // 0-11
76 unsigned int year
= dt
->year
- REFERENCE_YEAR
; // 0-99
77 int32_t unixTime
= (((year
/ 4 * (365 * 4 + 1) + days
[year
% 4][month
] + day
) * 24 + hour
) * 60 + minute
) * 60 + second
+ EPOCH_2000_OFFSET
;
78 return rtcTimeMake(unixTime
, dt
->millis
);
81 static void rtcTimeToDateTime(dateTime_t
*dt
, rtcTime_t t
)
83 int32_t unixTime
= t
/ MILLIS_PER_SECOND
- EPOCH_2000_OFFSET
;
84 dt
->seconds
= unixTime
% 60;
86 dt
->minutes
= unixTime
% 60;
88 dt
->hours
= unixTime
% 24;
91 unsigned int years
= unixTime
/ (365 * 4 + 1) * 4;
92 unixTime
%= 365 * 4 + 1;
95 for (year
= 3; year
> 0; year
--) {
96 if (unixTime
>= days
[year
][0]) {
102 for (month
= 11; month
> 0; month
--) {
103 if (unixTime
>= days
[year
][month
]) {
108 dt
->year
= years
+ year
+ REFERENCE_YEAR
;
109 dt
->month
= month
+ 1;
110 dt
->day
= unixTime
- days
[year
][month
] + 1;
111 dt
->millis
= t
% MILLIS_PER_SECOND
;
114 static void rtcGetDefaultDateTime(dateTime_t
*dateTime
)
120 dateTime
->minutes
= 0;
121 dateTime
->seconds
= 0;
122 dateTime
->millis
= 0;
125 static bool rtcIsDateTimeValid(dateTime_t
*dateTime
)
127 return (dateTime
->year
>= REFERENCE_YEAR
) &&
128 (dateTime
->month
>= 1 && dateTime
->month
<= 12) &&
129 (dateTime
->day
>= 1 && dateTime
->day
<= 31) &&
130 (dateTime
->hours
<= 23) &&
131 (dateTime
->minutes
<= 59) &&
132 (dateTime
->seconds
<= 59) &&
133 (dateTime
->millis
<= 999);
136 static void dateTimeWithOffset(dateTime_t
*dateTimeOffset
, dateTime_t
*dateTimeInitial
, int16_t minutes
)
138 rtcTime_t initialTime
= dateTimeToRtcTime(dateTimeInitial
);
139 rtcTime_t offsetTime
= rtcTimeMake(rtcTimeGetSeconds(&initialTime
) + minutes
* 60, rtcTimeGetMillis(&initialTime
));
140 rtcTimeToDateTime(dateTimeOffset
, offsetTime
);
143 static bool dateTimeFormat(char *buf
, dateTime_t
*dateTime
, int16_t offsetMinutes
, bool shortVersion
)
151 // Apply offset if necessary
152 if (offsetMinutes
!= 0) {
153 tz_hours
= offsetMinutes
/ 60;
154 tz_minutes
= abs(offsetMinutes
% 60);
155 dateTimeWithOffset(&local
, dateTime
, offsetMinutes
);
159 if (!rtcIsDateTimeValid(dateTime
)) {
160 rtcGetDefaultDateTime(&local
);
166 tfp_sprintf(buf
, "%04u-%02u-%02u %02u:%02u:%02u",
167 dateTime
->year
, dateTime
->month
, dateTime
->day
,
168 dateTime
->hours
, dateTime
->minutes
, dateTime
->seconds
);
170 // Changes to this format might require updates in
171 // dateTimeSplitFormatted()
172 // Datetime is in ISO_8601 format, https://en.wikipedia.org/wiki/ISO_8601
173 tfp_sprintf(buf
, "%04u-%02u-%02uT%02u:%02u:%02u.%03u%c%02d:%02d",
174 dateTime
->year
, dateTime
->month
, dateTime
->day
,
175 dateTime
->hours
, dateTime
->minutes
, dateTime
->seconds
, dateTime
->millis
,
176 tz_hours
>= 0 ? '+' : '-', abs(tz_hours
), tz_minutes
);
182 rtcTime_t
rtcTimeMake(int32_t secs
, uint16_t millis
)
184 return ((rtcTime_t
)secs
) * MILLIS_PER_SECOND
+ millis
;
187 int32_t rtcTimeGetSeconds(rtcTime_t
*t
)
189 return *t
/ MILLIS_PER_SECOND
;
192 uint16_t rtcTimeGetMillis(rtcTime_t
*t
)
194 return *t
% MILLIS_PER_SECOND
;
197 bool dateTimeFormatUTC(char *buf
, dateTime_t
*dt
)
199 return dateTimeFormat(buf
, dt
, 0, false);
202 bool dateTimeFormatLocal(char *buf
, dateTime_t
*dt
)
204 const int16_t timezoneOffset
= rtcIsDateTimeValid(dt
) ? timeConfig()->tz_offsetMinutes
: 0;
205 return dateTimeFormat(buf
, dt
, timezoneOffset
, false);
208 bool dateTimeFormatLocalShort(char *buf
, dateTime_t
*dt
)
210 return dateTimeFormat(buf
, dt
, timeConfig()->tz_offsetMinutes
, true);
213 void dateTimeUTCToLocal(dateTime_t
*utcDateTime
, dateTime_t
*localDateTime
)
215 dateTimeWithOffset(localDateTime
, utcDateTime
, timeConfig()->tz_offsetMinutes
);
218 bool dateTimeSplitFormatted(char *formatted
, char **date
, char **time
)
220 // Just look for the T and replace it with a zero
221 // XXX: Keep in sync with dateTimeFormat()
222 for (char *p
= formatted
; *p
; p
++) {
233 bool rtcHasTime(void)
238 bool rtcGet(rtcTime_t
*t
)
243 *t
= started
+ millis();
247 bool rtcSet(rtcTime_t
*t
)
249 started
= *t
- millis();
253 bool rtcGetDateTime(dateTime_t
*dt
)
257 rtcTimeToDateTime(dt
, t
);
260 // No time stored, fill dt with 0000-01-01T00:00:00.000
261 rtcGetDefaultDateTime(dt
);
265 bool rtcSetDateTime(dateTime_t
*dt
)
267 rtcTime_t t
= dateTimeToRtcTime(dt
);
271 #if defined(USE_PERSISTENT_OBJECTS)
272 void rtcPersistWrite(int16_t offsetMinutes
)
275 uint32_t highLongWord
= 0;
276 uint32_t lowLongWord
= 0;
277 if (rtcGet(&workTime
)) {
278 workTime
+= (offsetMinutes
* 60 * MILLIS_PER_SECOND
);
279 highLongWord
= (uint32_t)(workTime
>> 32);
280 lowLongWord
= (uint32_t)(workTime
& 0xffffffff);
282 persistentObjectWrite(PERSISTENT_OBJECT_RTC_HIGH
, highLongWord
);
283 persistentObjectWrite(PERSISTENT_OBJECT_RTC_LOW
, lowLongWord
);
286 bool rtcPersistRead(rtcTime_t
*t
)
288 const uint32_t highLongWord
= persistentObjectRead(PERSISTENT_OBJECT_RTC_HIGH
);
289 const uint32_t lowLongWord
= persistentObjectRead(PERSISTENT_OBJECT_RTC_LOW
);
291 if ((highLongWord
!= 0) || (lowLongWord
!= 0)) {
292 *t
= ((uint64_t)highLongWord
<< 32) + lowLongWord
;
299 #endif // USE_RTC_TIME