1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
|
/* Routines for formatting time
Copyright (C) 1995, 1996 Free Software Foundation, Inc.
Written by Miles Bader <miles@gnu.ai.mit.edu>
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2, or (at
your option) any later version.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */
#include <stdio.h>
#include <string.h>
#include <sys/time.h>
#include <time.h>
#include "timefmt.h"
#define SECOND 1
#define MINUTE 60
#define HOUR (60*MINUTE)
#define DAY (24*HOUR)
#define WEEK (7*DAY)
#define MONTH (31*DAY) /* Not strictly accurate, but oh well. */
#define YEAR (365*DAY) /* ditto */
/* Returns the number of digits in the integer N. */
static unsigned
int_len (unsigned n)
{
unsigned len = 1;
while (n >= 10)
{
n /= 10;
len++;
}
return len;
}
/* Returns TV1 divided by TV2. */
static unsigned
tv_div (struct timeval *tv1, struct timeval *tv2)
{
return
tv2->tv_sec
? tv1->tv_sec / tv2->tv_sec
: (tv1->tv_usec / tv2->tv_usec
+ (tv1->tv_sec ? tv1->tv_sec * 1000000 / tv2->tv_usec : 0));
}
/* Returns true if TV is zero. */
static inline int
tv_is_zero (struct timeval *tv)
{
return tv->tv_sec == 0 && tv->tv_usec == 0;
}
/* Returns true if TV1 >= TV2. */
static inline int
tv_is_ge (struct timeval *tv1, struct timeval *tv2)
{
return
tv1->tv_sec > tv2->tv_sec
|| (tv1->tv_sec == tv2->tv_sec && tv1->tv_usec >= tv2->tv_usec);
}
/* Format into BUF & BUF_LEN the time interval represented by TV, trying to
make the result less than WIDTH characters wide. The number of characters
used is returned. */
size_t
fmt_named_interval (struct timeval *tv, size_t width,
char *buf, size_t buf_len)
{
struct tscale
{
struct timeval thresh; /* Minimum time to use this scale. */
struct timeval unit; /* Unit this scale is based on. */
struct timeval frac_thresh; /* If a emitting a single digit of precision
will cause at least this much error, also
emit a single fraction digit. */
char *sfxs[5]; /* Names to use, in descending length. */
}
time_scales[] =
{
{{2*YEAR, 0}, {YEAR, 0}, {MONTH, 0},{" years", "years", "yrs", "y", 0 }},
{{3*MONTH, 0}, {MONTH, 0}, {WEEK, 0}, {" months","months","mo", 0 }},
{{2*WEEK, 0}, {WEEK, 0}, {DAY, 0}, {" weeks", "weeks", "wks", "w", 0 }},
{{2*DAY, 0}, {DAY, 0}, {HOUR, 0}, {" days", "days", "dys", "d", 0 }},
{{2*HOUR, 0}, {HOUR, 0}, {MINUTE, 0},{" hours","hours", "hrs", "h", 0 }},
{{2*MINUTE, 0},{MINUTE, 0},{1, 0}, {" minutes","min", "mi", "m", 0 }},
{{1, 100000}, {1, 0}, {0, 100000},{" seconds", "sec", "s", 0 }},
{{1, 0}, {1, 0}, {0, 0}, {" second", "sec", "s", 0 }},
{{0, 1100}, {0, 1000}, {0, 100}, {" milliseconds", "ms", 0 }},
{{0, 1000}, {0, 1000}, {0, 0}, {" millisecond", "ms", 0 }},
{{0, 2}, {0, 1}, {0, 0}, {" microseconds", "us", 0 }},
{{0, 1}, {0, 1}, {0, 0}, {" microsecond", "us", 0 }},
{{0, 0} }
};
struct tscale *ts = time_scales;
if (width <= 0 || width >= buf_len)
width = buf_len - 1;
for (ts = time_scales; !tv_is_zero (&ts->thresh); ts++)
if (tv_is_ge (tv, &ts->thresh))
{
char **sfx;
struct timeval *u = &ts->unit;
unsigned num = tv_div (tv, u);
unsigned frac = 0;
unsigned num_len = int_len (num);
if (num < 10
&& !tv_is_zero (&ts->frac_thresh)
&& tv_is_ge (tv, &ts->frac_thresh))
/* Calculate another place of prec, but only for low numbers. */
{
/* TV times 10. */
struct timeval tv10 =
{ tv->tv_sec * 10 + tv->tv_usec / 100000,
(tv->tv_usec % 100000) * 10 };
frac = tv_div (&tv10, u) - num * 10;
if (frac)
num_len += 2; /* Account for the extra `.' + DIGIT. */
}
/* While we have a choice, find a suffix that fits in WIDTH. */
for (sfx = ts->sfxs; sfx[1]; sfx++)
if (num_len + strlen (*sfx) <= width)
break;
if (!sfx[1] && frac)
/* We couldn't find a suffix that fits, and we're printing a
fraction digit. Sacrifice the fraction to make it fit. */
{
num_len -= 2;
frac = 0;
for (sfx = ts->sfxs; sfx[1]; sfx++)
if (num_len + strlen (*sfx) <= width)
break;
}
if (!sfx[1])
/* Still couldn't find a suffix that fits. Oh well, use the best. */
sfx--;
if (frac)
return snprintf (buf, buf_len, "%d.%d%s", num, frac, *sfx);
else
return snprintf (buf, buf_len, "%d%s", num, *sfx);
}
return sprintf (buf, "0"); /* Whatever */
}
/* Prints the number of units of size UNIT in *SECS, subtracting them from
*SECS, to BUF (the result *must* fit!), followed by SUFFIX; if the number
of units is zero, however, and *LEADING_ZEROS is false, print nothing (and
if something *is* printed, set *LEADING_ZEROS to true). MIN_WIDTH is the
minimum *total width* (including other fields) needed to print these
units. WIDTH is the amount of (total) space available. The number of
characters printed is returned. */
static size_t
add_field (int *secs, int unit, int *leading_zeros,
size_t min_width, char *suffix,
size_t width, char *buf)
{
int units = *secs / unit;
if (units || (width >= min_width && *leading_zeros))
{
*secs -= units * unit;
*leading_zeros = 1;
return
sprintf (buf,
(width == min_width ? "%d%s"
: width == min_width + 1 ? "%2d%s"
: "%02d%s"),
units, suffix);
}
else
return 0;
}
/* Format into BUF & BUF_LEN the time interval represented by TV, using
HH:MM:SS notation where possible, with FRAC_PLACES digits after the
decimal point, and trying to make the result less than WIDTH characters
wide. If LEADING_ZEROS is true, then any fields that are zero-valued, but
would fit in the given width are printed. If FRAC_PLACES is negative,
then any space remaining after printing the time, up to WIDTH, is used for
the fraction. The number of characters used is returned. */
size_t
fmt_seconds (struct timeval *tv, int leading_zeros, int frac_places,
size_t width, char *buf, size_t buf_len)
{
char *p = buf;
int secs = tv->tv_sec;
if (width <= 0 || width >= buf_len)
width = buf_len - 1;
if (tv->tv_sec > DAY)
return fmt_named_interval (tv, width, buf, buf_len);
if (frac_places > 0)
width -= frac_places + 1;
/* See if this time won't fit at all in fixed format. */
if ((secs > 10*HOUR && width < 8)
|| (secs > HOUR && width < 7)
|| (secs > 10*MINUTE && width < 5)
|| (secs > MINUTE && width < 4)
|| (secs > 10 && width < 2)
|| width < 1)
return fmt_named_interval (tv, width, buf, buf_len);
p += add_field (&secs, HOUR, &leading_zeros, 7, ":", width, p);
p += add_field (&secs, MINUTE, &leading_zeros, 4, ":", width, p);
p += add_field (&secs, SECOND, &leading_zeros, 1, "", width, p);
if (frac_places < 0 && (p - buf) < (int) width - 2)
/* If FRAC_PLACES is < 0, then use any space remaining before WIDTH. */
frac_places = width - (p - buf) - 1;
if (frac_places > 0)
/* Print fractions of a second. */
{
int frac = tv->tv_usec, i;
for (i = 6; i > frac_places; i--)
frac /= 10;
return (p - buf) + sprintf (p, ".%0*d", frac_places, frac);
}
else
return (p - buf);
}
/* Format into BUF & BUF_LEN the time interval represented by TV, using HH:MM
notation where possible, and trying to make the result less than WIDTH
characters wide. If LEADING_ZEROS is true, then any fields that are
zero-valued, but would fit in the given width are printed. The number of
characters used is returned. */
size_t
fmt_minutes (struct timeval *tv, int leading_zeros,
size_t width, char *buf, size_t buf_len)
{
char *p = buf;
int secs = tv->tv_sec;
if (width <= 0 || width >= buf_len)
width = buf_len - 1;
if (secs > DAY)
return fmt_named_interval (tv, width, buf, buf_len);
/* See if this time won't fit at all in fixed format. */
if ((secs > 10*HOUR && width < 5)
|| (secs > HOUR && width < 4)
|| (secs > 10*MINUTE && width < 2)
|| width < 1)
return fmt_named_interval (tv, width, buf, buf_len);
p += add_field (&secs, HOUR, &leading_zeros, 4, ":", width, p);
p += add_field (&secs, MINUTE, &leading_zeros, 1, "", width, p);
return p - buf;
}
/* Format into BUF & BUF_LEN the absolute time represented by TV. An attempt
is made to fit the result in less than WIDTH characters, by omitting
fields implied by the current time, NOW (if NOW is 0, then no assumption
is made, so the resulting times will be somewhat long). The number of
characters used is returned. */
size_t
fmt_past_time (struct timeval *tv, struct timeval *now,
size_t width, char *buf, size_t buf_len)
{
static char *time_fmts[] = { "%-r", "%-l:%M%p", "%-l%p", 0 };
static char *week_fmts[] = { "%A", "%a", 0 };
static char *month_fmts[] = { "%A %-d", "%a %-d", "%a%-d", 0 };
static char *date_fmts[] =
{ "%A, %-d %B", "%a, %-d %b", "%-d %B", "%-d %b", "%-d%b", 0 };
static char *year_fmts[] =
{ "%A, %-d %B %Y", "%a, %-d %b %Y", "%a, %-d %b %y", "%-d %b %y", "%-d%b%y", 0 };
struct tm tm;
int used = 0; /* Number of characters generated. */
long diff = now ? (now->tv_sec - tv->tv_sec) : tv->tv_sec;
if (diff < 0)
diff = -diff; /* XXX */
bcopy (localtime ((time_t *) &tv->tv_sec), &tm, sizeof tm);
if (width <= 0 || width >= buf_len)
width = buf_len - 1;
if (diff < DAY)
{
char **fmt;
for (fmt = time_fmts; *fmt && !used; fmt++)
used = strftime (buf, width + 1, *fmt, &tm);
if (! used)
/* Nothing worked, perhaps WIDTH is too small, but BUF_LEN will work.
We know FMT is one past the end the array, so FMT[-1] should be
the shortest possible option. */
used = strftime (buf, buf_len, fmt[-1], &tm);
}
else
{
static char *seps[] = { ", ", " ", "", 0 };
char **fmt, **dfmt, **dfmts, **sep;
if (diff < WEEK)
dfmts = week_fmts;
else if (diff < MONTH)
dfmts = month_fmts;
else if (diff < YEAR)
dfmts = date_fmts;
else
dfmts = year_fmts;
/* This ordering (date varying most quickly, then the separator, then
the time) preserves time detail as long as possible, and seems to
produce a graceful degradation of the result with decreasing widths. */
for (fmt = time_fmts; *fmt && !used; fmt++)
for (sep = seps; *sep && !used; sep++)
for (dfmt = dfmts; *dfmt && !used; dfmt++)
{
char whole_fmt[strlen (*dfmt) + strlen (*sep) + strlen (*fmt) + 1];
char *end = whole_fmt;
end = stpcpy (end, *dfmt);
end = stpcpy (end, *sep);
end = stpcpy (end, *fmt);
used = strftime (buf, width + 1, whole_fmt, &tm);
}
if (! used)
/* No concatenated formats worked, try just date formats. */
for (dfmt = dfmts; *dfmt && !used; dfmt++)
used = strftime (buf, width + 1, *dfmt, &tm);
if (! used)
/* Absolutely nothing has worked, perhaps WIDTH is too small, but
BUF_LEN will work. We know DFMT is one past the end the array, so
DFMT[-1] should be the shortest possible option. */
used = strftime (buf, buf_len, dfmt[-1], &tm);
}
return used;
}
|