Host C Specs


Directory: src/c/
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 96.3% 78 / 0 / 81
Functions: 100.0% 12 / 0 / 12
Branches: 75.0% 36 / 0 / 48

core/clock/timeband.c
Line Branch Exec Source
1 /**
2 * @file timeband.c
3 * @brief The window sums: build one, place a moment on it, and clip a span to it.
4 *
5 * @ingroup lib_core
6 */
7 #include "clock/timeband.h"
8
9 #include "math/scale.h"
10
11 /// Seconds in a minute, so the epoch maths reads as what it is
12 #define SECONDS_PER_MINUTE 60
13
14 /**
15 * @brief Bring any minute count into 0..1439, counting backwards from midnight for a negative one.
16 *
17 * @param minutes The count to wrap.
18 * @return The same moment of the day, 0 to 1439.
19 */
20 38 static int wrap_day(int minutes)
21 {
22 38 int wrapped = minutes % TIMEBAND_DAY_MINUTES;
23
2/2
✓ Branch 0 taken 9 times.
✓ Branch 1 taken 29 times.
38 return wrapped < 0 ? wrapped + TIMEBAND_DAY_MINUTES : wrapped;
24 }
25
26 /**
27 * @brief Whole minutes in a second count, rounding down so a part minute before the window still
28 * counts as the minute it started in.
29 *
30 * @param seconds The count, which may be negative.
31 * @return The count in minutes.
32 */
33 6 static int floor_minutes(int seconds)
34 {
35 6 int minutes = seconds / SECONDS_PER_MINUTE;
36
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
6 if (seconds % SECONDS_PER_MINUTE != 0 && seconds < 0)
37 {
38 ✗ minutes--;
39 }
40 6 return minutes;
41 }
42
43 /**
44 * @brief Whole minutes in a second count, rounding up so a span ending part way through a minute
45 * still covers it.
46 *
47 * @param seconds The count, which may be negative.
48 * @return The count in minutes.
49 */
50 6 static int ceil_minutes(int seconds)
51 {
52 6 int minutes = seconds / SECONDS_PER_MINUTE;
53
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
6 if (seconds % SECONDS_PER_MINUTE != 0 && seconds > 0)
54 {
55 ✗ minutes++;
56 }
57 6 return minutes;
58 }
59
60 10 TimeBand timeband_full_day(void)
61 {
62 10 return (TimeBand){.start_min = 0, .span_min = TIMEBAND_DAY_MINUTES};
63 }
64
65 3 TimeBand timeband_rolling(int now_min, int span_min, int lead_min)
66 {
67 3 int span = clamp_int(span_min, 1, TIMEBAND_DAY_MINUTES);
68 // the far end belongs to the next window, so the last minute is as far in as the moment can sit
69 3 int lead = clamp_int(lead_min, 0, span - 1);
70
71 3 return (TimeBand){.start_min = wrap_day(now_min - lead), .span_min = span};
72 }
73
74 4 TimeBand timeband_from_hour(int base_hour, int span_min)
75 {
76 4 int hour = wrap_day(base_hour * 60) / 60;
77
78 4 return (TimeBand){
79 4 .start_min = hour * 60,
80 4 .span_min = clamp_int(span_min, 1, TIMEBAND_DAY_MINUTES),
81 };
82 }
83
84 11 int timeband_offset(TimeBand band, int minute_of_day)
85 {
86 11 int offset = wrap_day(minute_of_day - band.start_min);
87
88 // the far end belongs to whatever comes next, so two windows laid end to end share no minute
89
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 3 times.
11 return offset < band.span_min ? offset : -1;
90 }
91
92 3 int timeband_pos(TimeBand band, int length, int minute_of_day)
93 {
94
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 2 times.
3 if (length <= 0)
95 {
96 1 return -1;
97 }
98
99 2 int offset = timeband_offset(band, minute_of_day);
100
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 return offset < 0 ? -1 : fraction_px(length, offset, band.span_min);
101 }
102
103 2 int timeband_pos_offset(TimeBand band, int length, int offset_min)
104 {
105
4/6
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 time.
✓ Branch 5 taken 1 time.
2 if (length <= 0 || offset_min < 0 || offset_min > band.span_min)
106 {
107 1 return -1;
108 }
109
110 // the span's own end is allowed here where a moment's is not: a block ending with the window
111 // has to reach the end of the axis rather than stopping a unit short of it
112 1 return fraction_px(length, offset_min, band.span_min);
113 }
114
115 10 bool timeband_clip(TimeBand band, time_t window_epoch, time_t start, time_t end,
116 TimeBandSpan *out)
117 {
118 10 time_t window_end = window_epoch + (time_t)band.span_min * SECONDS_PER_MINUTE;
119
120
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 9 times.
10 if (end < start)
121 {
122 1 end = start;
123 }
124
125 // an event pinned to a moment rather than a stretch still has to draw, so give it the minute
126 // it starts in. the flags below read the real end so this never reports as running off the band
127
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 8 times.
10 time_t occupied_end = (end == start) ? start + SECONDS_PER_MINUTE : end;
128
129
4/4
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 6 times.
10 if (occupied_end <= window_epoch || start >= window_end)
130 {
131 4 return false;
132 }
133
134 // pin to the window before the subtraction so a span from last week cannot make a minute count
135 // too big to hold
136 6 time_t first = start < window_epoch ? window_epoch : start;
137 6 time_t last = occupied_end > window_end ? window_end : occupied_end;
138
139 // both ends are pinned inside the window, so each is at most a day of seconds past its start
140 // an int holds that, and the watch then needs no 64-bit division. that is 754 bytes of libgcc
141 6 int from = floor_minutes((int)(first - window_epoch));
142 6 int to = ceil_minutes((int)(last - window_epoch));
143
144
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
6 if (to <= from)
145 {
146 ✗ to = from + 1;
147 }
148
149 6 out->from = from;
150 6 out->to = to;
151 6 out->clipped_start = start < window_epoch;
152 6 out->clipped_end = end > window_end;
153
154 6 return true;
155 }
156
157 6 int timeband_clip_daily(TimeBand band, int from_min, int to_min,
158 TimeBandSpan *out, int max_out)
159 {
160
4/6
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 5 times.
6 if (from_min < 0 || to_min < 0 || max_out <= 0)
161 {
162 1 return 0;
163 }
164
165 5 int start = wrap_day(from_min);
166 5 int length = wrap_day(to_min - from_min);
167
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 4 times.
5 if (length == 0)
168 {
169 // the two readings landing on the same minute is the sun never setting, not a span with
170 // no width, so it covers the whole day
171 1 length = TIMEBAND_DAY_MINUTES;
172 }
173
174 // the span repeats every day, so the window can catch the tail of yesterday's as well as
175 // today's. anything a day further out starts past the end of a window this short
176 5 int written = 0;
177
4/4
✓ Branch 0 taken 10 times.
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 9 times.
✓ Branch 3 taken 1 time.
14 for (int day = -1; day <= 0 && written < max_out; day++)
178 {
179 9 int at = wrap_day(start - band.start_min) + day * TIMEBAND_DAY_MINUTES;
180 9 int from = at < 0 ? 0 : at;
181 9 int to = (at + length) > band.span_min ? band.span_min : (at + length);
182
183
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 8 times.
9 if (to <= from)
184 {
185 1 continue;
186 }
187
188 8 out[written].from = from;
189 8 out[written].to = to;
190 8 out[written].clipped_start = at < 0;
191 8 out[written].clipped_end = (at + length) > band.span_min;
192 8 written++;
193 }
194
195 5 return written;
196 }
197
198 5 time_t timeband_window_epoch(TimeBand band, time_t now, int now_min)
199 {
200 // the top of the current minute. every time zone sits a whole number of minutes off UTC, so
201 // the seconds on the epoch are the seconds on the wall clock too
202 5 time_t minute_start = now - now % SECONDS_PER_MINUTE;
203
204 // while the window is open, its start is that many real minutes back
205 5 int in = timeband_offset(band, now_min);
206
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 1 time.
5 if (in >= 0)
207 {
208 4 return minute_start - (time_t)in * SECONDS_PER_MINUTE;
209 }
210
211 // not open yet, so it opens this many minutes ahead
212 1 int ahead = wrap_day(band.start_min - now_min);
213 1 return minute_start + (time_t)ahead * SECONDS_PER_MINUTE;
214 }
215