2 * This file is part of 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>
27 #include "common/maths.h"
28 #include "common/printf.h"
29 #include "common/time.h"
30 #include "common/utils.h"
32 #include "config/parameter_group_ids.h"
34 #include "drivers/time.h"
36 #include "fc/settings.h"
38 // For the "modulo 4" arithmetic to work, we need a leap base year
39 #define REFERENCE_YEAR 2000
40 // Offset (seconds) from the UNIX epoch (1970-01-01) to 2000-01-01
41 #define EPOCH_2000_OFFSET 946684800
43 #define MILLIS_PER_SECOND 1000
45 // rtcTime_t when the system was started.
46 // Calculated in rtcSet().
47 static rtcTime_t started
= 0;
49 static const uint16_t days
[4][12] =
51 { 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335},
52 { 366, 397, 425, 456, 486, 517, 547, 578, 609, 639, 670, 700},
53 { 731, 762, 790, 821, 851, 882, 912, 943, 974, 1004, 1035, 1065},
54 {1096, 1127, 1155, 1186, 1216, 1247, 1277, 1308, 1339, 1369, 1400, 1430},
57 PG_REGISTER_WITH_RESET_TEMPLATE(timeConfig_t
, timeConfig
, PG_TIME_CONFIG
, 1);
59 PG_RESET_TEMPLATE(timeConfig_t
, timeConfig
,
60 .tz_offset
= SETTING_TZ_OFFSET_DEFAULT
,
61 .tz_automatic_dst
= SETTING_TZ_AUTOMATIC_DST_DEFAULT
,
64 static rtcTime_t
dateTimeToRtcTime(const dateTime_t
*dt
)
66 unsigned int second
= dt
->seconds
; // 0-59
67 unsigned int minute
= dt
->minutes
; // 0-59
68 unsigned int hour
= dt
->hours
; // 0-23
69 unsigned int day
= dt
->day
- 1; // 0-30
70 unsigned int month
= dt
->month
- 1; // 0-11
71 unsigned int year
= dt
->year
- REFERENCE_YEAR
; // 0-99
72 int32_t unixTime
= (((year
/ 4 * (365 * 4 + 1) + days
[year
% 4][month
] + day
) * 24 + hour
) * 60 + minute
) * 60 + second
+ EPOCH_2000_OFFSET
;
73 return rtcTimeMake(unixTime
, dt
->millis
);
76 static void rtcTimeToDateTime(dateTime_t
*dt
, rtcTime_t t
)
78 int32_t unixTime
= t
/ MILLIS_PER_SECOND
- EPOCH_2000_OFFSET
;
79 dt
->seconds
= unixTime
% 60;
81 dt
->minutes
= unixTime
% 60;
83 dt
->hours
= unixTime
% 24;
86 unsigned int years
= unixTime
/ (365 * 4 + 1) * 4;
87 unixTime
%= 365 * 4 + 1;
90 for (year
= 3; year
> 0; year
--) {
91 if (unixTime
>= days
[year
][0]) {
97 for (month
= 11; month
> 0; month
--) {
98 if (unixTime
>= days
[year
][month
]) {
103 dt
->year
= years
+ year
+ REFERENCE_YEAR
;
104 dt
->month
= month
+ 1;
105 dt
->day
= unixTime
- days
[year
][month
] + 1;
106 dt
->millis
= t
% MILLIS_PER_SECOND
;
109 static void rtcGetDefaultDateTime(dateTime_t
*dateTime
)
115 dateTime
->minutes
= 0;
116 dateTime
->seconds
= 0;
117 dateTime
->millis
= 0;
120 static bool rtcIsDateTimeValid(dateTime_t
*dateTime
)
122 return (dateTime
->year
>= REFERENCE_YEAR
) &&
123 (dateTime
->month
>= 1 && dateTime
->month
<= 12) &&
124 (dateTime
->day
>= 1 && dateTime
->day
<= 31) &&
125 (dateTime
->hours
<= 23) &&
126 (dateTime
->minutes
<= 59) &&
127 (dateTime
->seconds
<= 59) &&
128 (dateTime
->millis
<= 999);
131 #if defined(RTC_AUTOMATIC_DST)
132 static int lastSundayOfMonth(int currentYear
, int wantedMonth
)
134 int days
[] = { 31 , 29 , 31 , 30 , 31 , 30 , 31 , 31 , 30 , 31 , 30 , 31 };
135 days
[1] -= (currentYear
% 4) || (!(currentYear
% 100) && (currentYear
% 400));
136 int w
= currentYear
* 365 + (currentYear
- 1) / 4 - (currentYear
- 1) / 100 + (currentYear
- 1) / 400 + 6;
138 for (int m
= 0; m
< 12; m
++) {
139 w
= (w
+ days
[m
]) % 7;
140 if (m
== wantedMonth
- 1) {
147 static int nthSundayOfMonth(int lastSunday
, int nth
)
149 while (lastSunday
> 7 * nth
) {
155 static bool isDST(rtcTime_t t
)
158 rtcTimeToDateTime(&dateTime
, t
);
160 switch ((tz_automatic_dst_e
) timeConfig()->tz_automatic_dst
) {
161 case TZ_AUTO_DST_OFF
:
163 case TZ_AUTO_DST_EU
: // begins at 1:00 a.m. on the last Sunday of March and ends at 1:00 a.m. on the last Sunday of October
164 if (dateTime
.month
< 3 || dateTime
.month
> 10) {
167 if (dateTime
.month
> 3 && dateTime
.month
< 10) {
170 lastSunday
= lastSundayOfMonth(dateTime
.year
, dateTime
.month
);
171 if ((dateTime
.day
< lastSunday
) || (dateTime
.day
> lastSunday
)) {
172 return !(dateTime
.month
== 3);
174 if (dateTime
.day
== lastSunday
) {
175 if (dateTime
.month
== 3) {
176 return dateTime
.hours
>= 1;
178 if (dateTime
.month
== 10) {
179 return dateTime
.hours
< 1;
183 case TZ_AUTO_DST_USA
: // begins at 2:00 a.m. on the second Sunday of March and ends at 2:00 a.m. on the first Sunday of November
184 if (dateTime
.month
< 3 || dateTime
.month
> 11) {
187 if (dateTime
.month
> 3 && dateTime
.month
< 11) {
190 lastSunday
= lastSundayOfMonth(dateTime
.year
, dateTime
.month
);
191 if (dateTime
.month
== 3) {
192 int secondSunday
= nthSundayOfMonth(lastSunday
, 2);
193 if (dateTime
.day
== secondSunday
) {
194 return dateTime
.hours
>= 2;
196 return dateTime
.day
> secondSunday
;
198 if (dateTime
.month
== 11) {
199 int firstSunday
= nthSundayOfMonth(lastSunday
, 1);
200 if (dateTime
.day
== firstSunday
) {
201 return dateTime
.hours
< 2;
203 return dateTime
.day
< firstSunday
;
211 static void dateTimeWithOffset(dateTime_t
*dateTimeOffset
, const dateTime_t
*dateTimeInitial
, int16_t *minutes
, bool automatic_dst
)
213 rtcTime_t initialTime
= dateTimeToRtcTime(dateTimeInitial
);
214 rtcTime_t offsetTime
= rtcTimeMake(rtcTimeGetSeconds(&initialTime
) + *minutes
* 60, rtcTimeGetMillis(&initialTime
));
215 #if defined(RTC_AUTOMATIC_DST)
216 if (automatic_dst
&& isDST(offsetTime
)) {
217 // Add one hour. Tell the caller that the
218 // offset has changed.
220 offsetTime
+= 60 * 60 * MILLIS_PER_SECOND
;
223 UNUSED(automatic_dst
);
225 rtcTimeToDateTime(dateTimeOffset
, offsetTime
);
228 static bool dateTimeFormat(char *buf
, dateTime_t
*dateTime
, int16_t offset
, bool automatic_dst
)
236 // Apply offset if necessary
237 if (offset
!= 0 || automatic_dst
) {
238 dateTimeWithOffset(&local
, dateTime
, &offset
, automatic_dst
);
239 tz_hours
= offset
/ 60;
240 tz_minutes
= ABS(offset
% 60);
244 if (!rtcIsDateTimeValid(dateTime
)) {
245 rtcGetDefaultDateTime(&local
);
250 // XXX: Changes to this format might require updates in
251 // dateTimeSplitFormatted()
252 tfp_sprintf(buf
, "%04u-%02u-%02uT%02u:%02u:%02u.%03u%c%02d:%02d",
253 dateTime
->year
, dateTime
->month
, dateTime
->day
,
254 dateTime
->hours
, dateTime
->minutes
, dateTime
->seconds
, dateTime
->millis
,
255 tz_hours
>= 0 ? '+' : '-', ABS(tz_hours
), tz_minutes
);
260 rtcTime_t
rtcTimeMake(int32_t secs
, uint16_t millis
)
262 return ((rtcTime_t
)secs
) * MILLIS_PER_SECOND
+ millis
;
265 int32_t rtcTimeGetSeconds(rtcTime_t
*t
)
267 return *t
/ MILLIS_PER_SECOND
;
270 uint16_t rtcTimeGetMillis(rtcTime_t
*t
)
272 return *t
% MILLIS_PER_SECOND
;
275 bool dateTimeFormatUTC(char *buf
, dateTime_t
*dt
)
277 return dateTimeFormat(buf
, dt
, 0, false);
280 bool dateTimeFormatLocal(char *buf
, dateTime_t
*dt
)
282 return dateTimeFormat(buf
, dt
, timeConfig()->tz_offset
, true);
285 void dateTimeUTCToLocal(dateTime_t
*localDateTime
, const dateTime_t
*utcDateTime
)
287 int16_t offset
= timeConfig()->tz_offset
;
288 dateTimeWithOffset(localDateTime
, utcDateTime
, &offset
, true);
291 bool dateTimeSplitFormatted(char *formatted
, char **date
, char **time
)
293 // Just look for the T and replace it with a zero
294 // XXX: Keep in sync with dateTimeFormat()
295 for (char *p
= formatted
; *p
; p
++) {
306 bool rtcHasTime(void)
311 bool rtcGet(rtcTime_t
*t
)
316 *t
= started
+ millis();
320 bool rtcSet(rtcTime_t
*t
)
322 started
= *t
- millis();
326 bool rtcGetDateTime(dateTime_t
*dt
)
330 rtcTimeToDateTime(dt
, t
);
333 // No time stored, fill dt with 0000-01-01T00:00:00.000
334 rtcGetDefaultDateTime(dt
);
338 bool rtcGetDateTimeLocal(dateTime_t
*dt
)
340 if (rtcGetDateTime(dt
)) {
341 dateTimeUTCToLocal(dt
, dt
);
347 bool rtcSetDateTime(dateTime_t
*dt
)
349 rtcTime_t t
= dateTimeToRtcTime(dt
);