1 /*
2 * Copyright (c) 2019 Peter Bigot Consulting, LLC
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7 /*
8 * The time_days_from_civil function is derived directly from public
9 * domain content written by Howard Hinnant and available at:
10 * http://howardhinnant.github.io/date_algorithms.html#days_from_civil
11 */
12
13 #include <errno.h>
14 #include <stdbool.h>
15 #include <stddef.h>
16 #include <stdint.h>
17 #include <time.h>
18
19 #include <zephyr/sys/__assert.h>
20 #include <zephyr/sys/clock.h>
21 #include <zephyr/sys/timeutil.h>
22
23 /** Convert a civil (proleptic Gregorian) date to days relative to
24 * 1970-01-01.
25 *
26 * @param y the calendar year
27 * @param m the calendar month, in the range [1, 12]
28 * @param d the day of the month, in the range [1, last_day_of_month(y, m)]
29 *
30 * @return the signed number of days between the specified day and
31 * 1970-01-01
32 *
33 * @see http://howardhinnant.github.io/date_algorithms.html#days_from_civil
34 */
time_days_from_civil(int64_t y,unsigned int m,unsigned int d)35 static int64_t time_days_from_civil(int64_t y,
36 unsigned int m,
37 unsigned int d)
38 {
39 y -= m <= 2;
40
41 int64_t era = ((y >= 0) ? y : (y - 399)) / 400;
42 unsigned int yoe = y - era * 400;
43 unsigned int doy = (153U * (m + ((m > 2) ? -3 : 9)) + 2U) / 5U + d;
44 unsigned int doe = yoe * 365U + yoe / 4U - yoe / 100U + doy;
45
46 return era * 146097 + (time_t)doe - 719468;
47 }
48
timeutil_timegm64(const struct tm * tm)49 int64_t timeutil_timegm64(const struct tm *tm)
50 {
51 int64_t y = TIME_UTILS_BASE_YEAR + (int64_t)tm->tm_year;
52 unsigned int m = tm->tm_mon + 1;
53 unsigned int d = tm->tm_mday - 1;
54 int64_t ndays = time_days_from_civil(y, m, d);
55 int64_t time = tm->tm_sec;
56
57 time += 60LL * (tm->tm_min + 60LL * tm->tm_hour);
58 time += 86400LL * ndays;
59
60 return time;
61 }
62
timeutil_timegm(const struct tm * tm)63 time_t timeutil_timegm(const struct tm *tm)
64 {
65 int64_t time = timeutil_timegm64(tm);
66 time_t rv = (time_t)time;
67
68 errno = 0;
69 if ((sizeof(rv) == sizeof(int32_t))
70 && ((time < (int64_t)INT32_MIN)
71 || (time > (int64_t)INT32_MAX))) {
72 errno = ERANGE;
73 rv = -1;
74 }
75
76 return rv;
77 }
78
timeutil_sync_state_update(struct timeutil_sync_state * tsp,const struct timeutil_sync_instant * inst)79 int timeutil_sync_state_update(struct timeutil_sync_state *tsp,
80 const struct timeutil_sync_instant *inst)
81 {
82 int rv = -EINVAL;
83
84 if (((tsp->base.ref == 0) && (inst->ref > 0))
85 || ((inst->ref > tsp->base.ref)
86 && (inst->local > tsp->base.local))) {
87 if (tsp->base.ref == 0) {
88 tsp->base = *inst;
89 tsp->latest = (struct timeutil_sync_instant){};
90 tsp->skew = 1.0f;
91 rv = 0;
92 } else {
93 tsp->latest = *inst;
94 rv = 1;
95 }
96 }
97
98 return rv;
99 }
100
timeutil_sync_state_set_skew(struct timeutil_sync_state * tsp,float skew,const struct timeutil_sync_instant * base)101 int timeutil_sync_state_set_skew(struct timeutil_sync_state *tsp, float skew,
102 const struct timeutil_sync_instant *base)
103 {
104 int rv = -EINVAL;
105
106 if (skew > 0) {
107 tsp->skew = skew;
108 if (base != NULL) {
109 tsp->base = *base;
110 tsp->latest = (struct timeutil_sync_instant){};
111 }
112 rv = 0;
113 }
114
115 return rv;
116 }
117
timeutil_sync_estimate_skew(const struct timeutil_sync_state * tsp)118 float timeutil_sync_estimate_skew(const struct timeutil_sync_state *tsp)
119 {
120 float rv = 0;
121
122 if ((tsp->base.ref != 0) && (tsp->latest.ref != 0)
123 && (tsp->latest.local > tsp->base.local)) {
124 const struct timeutil_sync_config *cfg = tsp->cfg;
125 double ref_delta = tsp->latest.ref - tsp->base.ref;
126 double local_delta = tsp->latest.local - tsp->base.local;
127
128 rv = ref_delta * cfg->local_Hz / local_delta / cfg->ref_Hz;
129 }
130
131 return rv;
132 }
133
timeutil_sync_ref_from_local(const struct timeutil_sync_state * tsp,uint64_t local,uint64_t * refp)134 int timeutil_sync_ref_from_local(const struct timeutil_sync_state *tsp,
135 uint64_t local, uint64_t *refp)
136 {
137 int rv = -EINVAL;
138
139 if ((tsp->skew > 0) && (tsp->base.ref > 0) && (refp != NULL)) {
140 const struct timeutil_sync_config *cfg = tsp->cfg;
141 int64_t local_delta = local - tsp->base.local;
142 #ifdef CONFIG_TIMEUTIL_APPLY_SKEW
143 /* (x * 1.0) != x for large values of x.
144 * Therefore only apply the multiplication if the skew is not one.
145 */
146 if (tsp->skew != 1.0f) {
147 local_delta *= (double)tsp->skew;
148 }
149 #endif /* CONFIG_TIMEUTIL_APPLY_SKEW */
150 int64_t ref_delta = local_delta * cfg->ref_Hz / cfg->local_Hz;
151 int64_t ref_abs = (int64_t)tsp->base.ref + ref_delta;
152
153 if (ref_abs < 0) {
154 rv = -ERANGE;
155 } else {
156 *refp = ref_abs;
157 rv = (tsp->skew != 1.0f) ? 1 : 0;
158 }
159 }
160
161 return rv;
162 }
163
timeutil_sync_local_from_ref(const struct timeutil_sync_state * tsp,uint64_t ref,int64_t * localp)164 int timeutil_sync_local_from_ref(const struct timeutil_sync_state *tsp,
165 uint64_t ref, int64_t *localp)
166 {
167 int rv = -EINVAL;
168
169 if ((tsp->skew > 0) && (tsp->base.ref > 0) && (localp != NULL)) {
170 const struct timeutil_sync_config *cfg = tsp->cfg;
171 int64_t ref_delta = (int64_t)(ref - tsp->base.ref);
172 int64_t local_delta = (ref_delta * cfg->local_Hz) / cfg->ref_Hz;
173 #ifdef CONFIG_TIMEUTIL_APPLY_SKEW
174 /* (x / 1.0) != x for large values of x.
175 * Therefore only apply the division if the skew is not one.
176 */
177 if (tsp->skew != 1.0f) {
178 local_delta /= (double)tsp->skew;
179 }
180 #endif /* CONFIG_TIMEUTIL_APPLY_SKEW */
181 int64_t local_abs = (int64_t)tsp->base.local
182 + (int64_t)local_delta;
183
184 *localp = local_abs;
185 rv = (tsp->skew != 1.0f) ? 1 : 0;
186 }
187
188 return rv;
189 }
190
timeutil_sync_skew_to_ppb(float skew)191 int32_t timeutil_sync_skew_to_ppb(float skew)
192 {
193 int64_t ppb64 = (int64_t)((1.0 - (double)skew) * 1E9);
194 int32_t ppb32 = (int32_t)ppb64;
195
196 return (ppb64 == ppb32) ? ppb32 : INT32_MIN;
197 }
198
timespec_normalize(struct timespec * ts)199 bool timespec_normalize(struct timespec *ts)
200 {
201 __ASSERT_NO_MSG(ts != NULL);
202
203 long sec;
204
205 if (ts->tv_nsec >= (long)NSEC_PER_SEC) {
206 sec = ts->tv_nsec / (long)NSEC_PER_SEC;
207 } else if (ts->tv_nsec < 0) {
208 sec = DIV_ROUND_UP((unsigned long)-ts->tv_nsec, NSEC_PER_SEC);
209 } else {
210 sec = 0;
211 }
212
213 if ((ts->tv_nsec < 0) && (ts->tv_sec < 0) && (ts->tv_sec - SYS_TIME_T_MIN < sec)) {
214 /*
215 * When `tv_nsec` is negative and `tv_sec` is already most negative,
216 * further subtraction would cause integer overflow.
217 */
218 return false;
219 }
220
221 if ((ts->tv_nsec >= (long)NSEC_PER_SEC) && (ts->tv_sec > 0) &&
222 (SYS_TIME_T_MAX - ts->tv_sec < sec)) {
223 /*
224 * When `tv_nsec` is >= `NSEC_PER_SEC` and `tv_sec` is already most
225 * positive, further addition would cause integer overflow.
226 */
227 return false;
228 }
229
230 if (ts->tv_nsec >= (long)NSEC_PER_SEC) {
231 ts->tv_sec += sec;
232 ts->tv_nsec -= sec * (long)NSEC_PER_SEC;
233 } else if (ts->tv_nsec < 0) {
234 ts->tv_sec -= sec;
235 ts->tv_nsec += sec * (long)NSEC_PER_SEC;
236 } else {
237 /* no change: SonarQube was complaining */
238 }
239
240 __ASSERT_NO_MSG(timespec_is_valid(ts));
241
242 return true;
243 }
244