1 | |
2 | |
3 | |
4 | |
5 | |
6 | |
7 | |
8 | |
9 | |
10 | |
11 | |
12 | |
13 | |
14 | |
15 | |
16 | |
17 | |
18 | |
19 | |
20 | |
21 | #include <stdio.h> |
22 | #include <string.h> |
23 | #include <sys/time.h> |
24 | #include <time.h> |
25 | |
26 | #include "timefmt.h" |
27 | |
28 | #define SECOND1 1 |
29 | #define MINUTE60 60 |
30 | #define HOUR(60*60) (60*MINUTE60) |
31 | #define DAY(24*(60*60)) (24*HOUR(60*60)) |
32 | #define WEEK(7*(24*(60*60))) (7*DAY(24*(60*60))) |
33 | #define MONTH(31*(24*(60*60))) (31*DAY(24*(60*60))) /* Not strictly accurate, but oh well. */ |
34 | #define YEAR(365*(24*(60*60))) (365*DAY(24*(60*60))) /* ditto */ |
35 |
|
36 | |
37 | static unsigned |
38 | int_len (unsigned n) |
39 | { |
40 | unsigned len = 1; |
41 | while (n >= 10) |
42 | { |
43 | n /= 10; |
44 | len++; |
45 | } |
46 | return len; |
47 | } |
48 | |
49 | |
50 | static unsigned |
51 | tv_div (struct timeval *tv1, struct timeval *tv2) |
52 | { |
53 | return |
54 | tv2->tv_sec |
55 | ? tv1->tv_sec / tv2->tv_sec |
56 | : (tv1->tv_usec / tv2->tv_usec |
57 | + (tv1->tv_sec ? tv1->tv_sec * 1000000 / tv2->tv_usec : 0)); |
58 | } |
59 | |
60 | |
61 | static inline int |
62 | tv_is_zero (struct timeval *tv) |
63 | { |
64 | return tv->tv_sec == 0 && tv->tv_usec == 0; |
65 | } |
66 | |
67 | |
68 | static inline int |
69 | tv_is_ge (struct timeval *tv1, struct timeval *tv2) |
70 | { |
71 | return |
72 | tv1->tv_sec > tv2->tv_sec |
73 | || (tv1->tv_sec == tv2->tv_sec && tv1->tv_usec >= tv2->tv_usec); |
74 | } |
75 |
|
76 | |
77 | |
78 | |
79 | size_t |
80 | fmt_named_interval (struct timeval *tv, size_t width, |
81 | char *buf, size_t buf_len) |
82 | { |
83 | struct tscale |
84 | { |
85 | struct timeval thresh; |
86 | struct timeval unit; |
87 | struct timeval frac_thresh; |
88 | |
89 | |
90 | char *sfxs[5]; |
91 | } |
92 | time_scales[] = |
93 | { |
94 | {{2*YEAR(365*(24*(60*60))), 0}, {YEAR(365*(24*(60*60))), 0}, {MONTH(31*(24*(60*60))), 0},{" years", "years", "yrs", "y", 0 }}, |
95 | {{3*MONTH(31*(24*(60*60))), 0}, {MONTH(31*(24*(60*60))), 0}, {WEEK(7*(24*(60*60))), 0}, {" months","months","mo", 0 }}, |
96 | {{2*WEEK(7*(24*(60*60))), 0}, {WEEK(7*(24*(60*60))), 0}, {DAY(24*(60*60)), 0}, {" weeks", "weeks", "wks", "w", 0 }}, |
97 | {{2*DAY(24*(60*60)), 0}, {DAY(24*(60*60)), 0}, {HOUR(60*60), 0}, {" days", "days", "dys", "d", 0 }}, |
98 | {{2*HOUR(60*60), 0}, {HOUR(60*60), 0}, {MINUTE60, 0},{" hours","hours", "hrs", "h", 0 }}, |
99 | {{2*MINUTE60, 0},{MINUTE60, 0},{1, 0}, {" minutes","min", "mi", "m", 0 }}, |
100 | {{1, 100000}, {1, 0}, {0, 100000},{" seconds", "sec", "s", 0 }}, |
101 | {{1, 0}, {1, 0}, {0, 0}, {" second", "sec", "s", 0 }}, |
102 | {{0, 1100}, {0, 1000}, {0, 100}, {" milliseconds", "ms", 0 }}, |
103 | {{0, 1000}, {0, 1000}, {0, 0}, {" millisecond", "ms", 0 }}, |
104 | {{0, 2}, {0, 1}, {0, 0}, {" microseconds", "us", 0 }}, |
105 | {{0, 1}, {0, 1}, {0, 0}, {" microsecond", "us", 0 }}, |
106 | {{0, 0} } |
107 | }; |
108 | struct tscale *ts = time_scales; |
| Value stored to 'ts' during its initialization is never read |
109 | |
110 | if (width <= 0 || width >= buf_len) |
111 | width = buf_len - 1; |
112 | |
113 | for (ts = time_scales; !tv_is_zero (&ts->thresh); ts++) |
114 | if (tv_is_ge (tv, &ts->thresh)) |
115 | { |
116 | char **sfx; |
117 | struct timeval *u = &ts->unit; |
118 | unsigned num = tv_div (tv, u); |
119 | unsigned frac = 0; |
120 | unsigned num_len = int_len (num); |
121 | |
122 | if (num < 10 |
123 | && !tv_is_zero (&ts->frac_thresh) |
124 | && tv_is_ge (tv, &ts->frac_thresh)) |
125 | |
126 | { |
127 | |
128 | struct timeval tv10 = |
129 | { tv->tv_sec * 10 + tv->tv_usec / 100000, |
130 | (tv->tv_usec % 100000) * 10 }; |
131 | frac = tv_div (&tv10, u) - num * 10; |
132 | if (frac) |
133 | num_len += 2; |
134 | } |
135 | |
136 | |
137 | for (sfx = ts->sfxs; sfx[1]; sfx++) |
138 | if (num_len + strlen (*sfx) <= width) |
139 | break; |
140 | |
141 | if (!sfx[1] && frac) |
142 | |
143 | |
144 | { |
145 | num_len -= 2; |
146 | frac = 0; |
147 | for (sfx = ts->sfxs; sfx[1]; sfx++) |
148 | if (num_len + strlen (*sfx) <= width) |
149 | break; |
150 | } |
151 | |
152 | if (!sfx[1]) |
153 | |
154 | sfx--; |
155 | |
156 | if (frac) |
157 | return snprintf (buf, buf_len, "%d.%d%s", num, frac, *sfx); |
158 | else |
159 | return snprintf (buf, buf_len, "%d%s", num, *sfx); |
160 | } |
161 | |
162 | return sprintf (buf, "0"); |
163 | } |
164 |
|
165 | |
166 | |
167 | |
168 | |
169 | |
170 | |
171 | |
172 | static size_t |
173 | add_field (int *secs, int unit, int *leading_zeros, |
174 | size_t min_width, char *suffix, |
175 | size_t width, char *buf) |
176 | { |
177 | int units = *secs / unit; |
178 | if (units || (width >= min_width && *leading_zeros)) |
179 | { |
180 | *secs -= units * unit; |
181 | *leading_zeros = 1; |
182 | return |
183 | sprintf (buf, |
184 | (width == min_width ? "%d%s" |
185 | : width == min_width + 1 ? "%2d%s" |
186 | : "%02d%s"), |
187 | units, suffix); |
188 | } |
189 | else |
190 | return 0; |
191 | } |
192 |
|
193 | |
194 | |
195 | |
196 | |
197 | |
198 | |
199 | |
200 | size_t |
201 | fmt_seconds (struct timeval *tv, int leading_zeros, int frac_places, |
202 | size_t width, char *buf, size_t buf_len) |
203 | { |
204 | char *p = buf; |
205 | int secs = tv->tv_sec; |
206 | |
207 | if (width <= 0 || width >= buf_len) |
208 | width = buf_len - 1; |
209 | |
210 | if (tv->tv_sec > DAY(24*(60*60))) |
211 | return fmt_named_interval (tv, width, buf, buf_len); |
212 | |
213 | if (frac_places > 0) |
214 | width -= frac_places + 1; |
215 | |
216 | |
217 | if ((secs > 10*HOUR(60*60) && width < 8) |
218 | || (secs > HOUR(60*60) && width < 7) |
219 | || (secs > 10*MINUTE60 && width < 5) |
220 | || (secs > MINUTE60 && width < 4) |
221 | || (secs > 10 && width < 2) |
222 | || width < 1) |
223 | return fmt_named_interval (tv, width, buf, buf_len); |
224 | |
225 | p += add_field (&secs, HOUR(60*60), &leading_zeros, 7, ":", width, p); |
226 | p += add_field (&secs, MINUTE60, &leading_zeros, 4, ":", width, p); |
227 | p += add_field (&secs, SECOND1, &leading_zeros, 1, "", width, p); |
228 | |
229 | if (frac_places < 0 && (p - buf) < (int) width - 2) |
230 | |
231 | frac_places = width - (p - buf) - 1; |
232 | |
233 | if (frac_places > 0) |
234 | |
235 | { |
236 | int frac = tv->tv_usec, i; |
237 | for (i = 6; i > frac_places; i--) |
238 | frac /= 10; |
239 | return (p - buf) + sprintf (p, ".%0*d", frac_places, frac); |
240 | } |
241 | else |
242 | return (p - buf); |
243 | } |
244 |
|
245 | |
246 | |
247 | |
248 | |
249 | |
250 | size_t |
251 | fmt_minutes (struct timeval *tv, int leading_zeros, |
252 | size_t width, char *buf, size_t buf_len) |
253 | { |
254 | char *p = buf; |
255 | int secs = tv->tv_sec; |
256 | |
257 | if (width <= 0 || width >= buf_len) |
258 | width = buf_len - 1; |
259 | |
260 | if (secs > DAY(24*(60*60))) |
261 | return fmt_named_interval (tv, width, buf, buf_len); |
262 | |
263 | |
264 | if ((secs > 10*HOUR(60*60) && width < 5) |
265 | || (secs > HOUR(60*60) && width < 4) |
266 | || (secs > 10*MINUTE60 && width < 2) |
267 | || width < 1) |
268 | return fmt_named_interval (tv, width, buf, buf_len); |
269 | |
270 | p += add_field (&secs, HOUR(60*60), &leading_zeros, 4, ":", width, p); |
271 | p += add_field (&secs, MINUTE60, &leading_zeros, 1, "", width, p); |
272 | |
273 | return p - buf; |
274 | } |
275 |
|
276 | |
277 | |
278 | |
279 | |
280 | |
281 | size_t |
282 | fmt_past_time (struct timeval *tv, struct timeval *now, |
283 | size_t width, char *buf, size_t buf_len) |
284 | { |
285 | static char *time_fmts[] = { "%-r", "%-l:%M%p", "%-l%p", 0 }; |
286 | static char *week_fmts[] = { "%A", "%a", 0 }; |
287 | static char *month_fmts[] = { "%A %-d", "%a %-d", "%a%-d", 0 }; |
288 | static char *date_fmts[] = |
289 | { "%A, %-d %B", "%a, %-d %b", "%-d %B", "%-d %b", "%-d%b", 0 }; |
290 | static char *year_fmts[] = |
291 | { "%A, %-d %B %Y", "%a, %-d %b %Y", "%a, %-d %b %y", "%-d %b %y", "%-d%b%y", 0 }; |
292 | struct tm tm; |
293 | int used = 0; |
294 | long diff = now ? (now->tv_sec - tv->tv_sec) : tv->tv_sec; |
295 | |
296 | if (diff < 0) |
297 | diff = -diff; |
298 | |
299 | bcopy (localtime ((time_t *) &tv->tv_sec), &tm, sizeof tm); |
300 | |
301 | if (width <= 0 || width >= buf_len) |
302 | width = buf_len - 1; |
303 | |
304 | if (diff < DAY(24*(60*60))) |
305 | { |
306 | char **fmt; |
307 | for (fmt = time_fmts; *fmt && !used; fmt++) |
308 | used = strftime (buf, width + 1, *fmt, &tm); |
309 | if (! used) |
310 | |
311 | |
312 | |
313 | used = strftime (buf, buf_len, fmt[-1], &tm); |
314 | } |
315 | else |
316 | { |
317 | static char *seps[] = { ", ", " ", "", 0 }; |
318 | char **fmt, **dfmt, **dfmts, **sep; |
319 | |
320 | if (diff < WEEK(7*(24*(60*60)))) |
321 | dfmts = week_fmts; |
322 | else if (diff < MONTH(31*(24*(60*60)))) |
323 | dfmts = month_fmts; |
324 | else if (diff < YEAR(365*(24*(60*60)))) |
325 | dfmts = date_fmts; |
326 | else |
327 | dfmts = year_fmts; |
328 | |
329 | |
330 | |
331 | |
332 | for (fmt = time_fmts; *fmt && !used; fmt++) |
333 | for (sep = seps; *sep && !used; sep++) |
334 | for (dfmt = dfmts; *dfmt && !used; dfmt++) |
335 | { |
336 | char whole_fmt[strlen (*dfmt) + strlen (*sep) + strlen (*fmt) + 1]; |
337 | char *end = whole_fmt; |
338 | |
339 | end = stpcpy (end, *dfmt); |
340 | end = stpcpy (end, *sep); |
341 | stpcpy (end, *fmt); |
342 | |
343 | used = strftime (buf, width + 1, whole_fmt, &tm); |
344 | } |
345 | |
346 | if (! used) |
347 | |
348 | for (dfmt = dfmts; *dfmt && !used; dfmt++) |
349 | used = strftime (buf, width + 1, *dfmt, &tm); |
350 | |
351 | if (! used) |
352 | |
353 | |
354 | |
355 | used = strftime (buf, buf_len, dfmt[-1], &tm); |
356 | } |
357 | |
358 | return used; |
359 | } |