diff options
-rw-r--r-- | libshouldbeinlibc/timefmt.c | 280 |
1 files changed, 170 insertions, 110 deletions
diff --git a/libshouldbeinlibc/timefmt.c b/libshouldbeinlibc/timefmt.c index 69f8590f..ade1e6c6 100644 --- a/libshouldbeinlibc/timefmt.c +++ b/libshouldbeinlibc/timefmt.c @@ -22,47 +22,16 @@ #include <string.h> #include <sys/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 */ - -/* XXX move this somewhere else. */ -int -fmt_frac_value (int value, unsigned min_value_len, - unsigned frac, unsigned frac_scale, - unsigned width, char *buf, unsigned buf_len) -{ - unsigned value_len; - unsigned frac_len; - unsigned len = 0; - - if (value >= 100) /* the integer part */ - value_len = 3; - else if (value >= 10) - value_len = 2; - else - value_len = 1; - - while (value_len < min_value_len-- && len < buf_len) - *buf++ = '0', len++; - - for (frac_len = frac_scale - ; frac_len > 0 && (width < value_len + 1 + frac_len || frac % 10 == 0) - ; frac_len--) - frac /= 10; - - if (frac_len > 0) - len += - snprintf (buf + len, buf_len - len, "%d.%0*d", value, frac_len, frac); - else - len += snprintf (buf + len, buf_len - len, "%d", value); - - return len; -} - + /* Returns the number of digits in the integer N. */ static unsigned int_len (unsigned n) @@ -86,12 +55,12 @@ tv_div (struct timeval *tv1, struct timeval *tv2) : (tv1->tv_usec / tv2->tv_usec + (tv1->tv_sec ? tv1->tv_sec * 1000000 / tv2->tv_usec : 0)); } - + /* 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. */ -int -fmt_named_interval (struct timeval *tv, int width, char *buf, unsigned buf_len) +size_t +fmt_named_interval (struct timeval *tv, size_t width, char *buf, size_t buf_len) { struct tscale { @@ -157,106 +126,197 @@ fmt_named_interval (struct timeval *tv, int width, char *buf, unsigned buf_len) return sprintf (buf, "0"); /* Whatever */ } - -static int -append_fraction (int frac, int digits, char *buf, unsigned max) + +/* 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 slen = strlen (buf); - int left = max - slen; - if (left > 1) + int units = *secs / unit; + if (units || (width >= min_width && *leading_zeros)) { - buf[slen] = '.'; - left--; - while (digits > left) - frac /= 10, digits--; - sprintf (buf + slen + 1, "%0*d", digits, frac); - return slen + 1 + digits; + *secs -= units; + *leading_zeros = 1; + return + sprintf (buf, + (width == min_width ? "%d%s" + : width == min_width + 1 ? "%2d%s" + : "%02d%s"), + units, suffix); } else - return slen; + return 0; } - + /* Format into BUF & BUF_LEN the time interval represented by TV, using - HH:MM:SS notation where possible, and trying to make the result less than - WIDTH characters wide. The number of characters used is returned. */ -int -fmt_seconds (struct timeval *tv, unsigned width, char *buf, unsigned buf_len) + 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 (tv->tv_sec > HOUR) - if (width >= 8) - /* H:MM:SS.ss... */ - return snprintf (buf, buf_len, "%2d:%02d:%02d", - tv->tv_sec / HOUR, - (tv->tv_sec % HOUR) / MINUTE, (tv->tv_sec % MINUTE)) - + append_fraction (tv->tv_usec, 6, buf, width); - else - return fmt_named_interval (tv, width, buf, buf_len); - else if (width >= 5 || tv->tv_sec > MINUTE) - /* M:SS.ss... */ - return snprintf (buf, buf_len, "%2d:%02d", - tv->tv_sec / MINUTE, tv->tv_sec % MINUTE) - + append_fraction (tv->tv_usec, 6, buf, width); + 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) < 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; + *p++ = '.'; + return (p - buf) + sprintf (p, ".%0*d", frac_places, frac); + } else - return fmt_frac_value (tv->tv_sec, 1, tv->tv_usec, 6, width, buf, buf_len); + 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. The number of characters used is returned. */ -int -fmt_minutes (struct timeval *tv, int width, char *buf, unsigned buf_len) + +/* 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 (tv->tv_sec > DAY) + if (secs > DAY) return fmt_named_interval (tv, width, buf, buf_len); - if (width >= 8) - /* H:MM:SS.ss... */ - return snprintf (buf, buf_len, "%2d:%02d:%02d", - tv->tv_sec / HOUR, - (tv->tv_sec % HOUR) / MINUTE, (tv->tv_sec % MINUTE)) - + append_fraction (tv->tv_usec, 6, buf, width); - else if (width >= 5 || (width >= 4 && tv->tv_sec < 10 * HOUR)) - /* H:MM */ - return sprintf (buf, "%2d:%02d", - tv->tv_sec / HOUR, (tv->tv_sec % HOUR) / MINUTE); - else if ((tv->tv_sec >= 100 * MINUTE && width < 3) || tv->tv_sec > 3 * HOUR) - /* H: */ - return snprintf (buf, buf_len, "%2d:", tv->tv_sec / HOUR); - else - /* M */ - return snprintf (buf, buf_len, "%2d", tv->tv_sec / MINUTE); -} + /* 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); -#if 0 -int + 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, - int width, char *buf, unsigned buf_len) + size_t width, char *buf, size_t buf_len) { - struct tm tm, now_tm; - char day[10], month[10], time[20]; - long diff = now->tv_sec - tv->tv_sec; + static char *time_fmts[] = { "%-r", "%l:%M%p", "%l%p", 0 }; + static char *week_fmts[] = { "%A,", "%a,", "%a", 0 }; + static char *month_fmts[] = { "%A, %-d", "%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 (tm, localtime (&tv.tv_sec), sizeof (tm)); - bcopy (now_tm, localtime (&now.tv_sec), sizeof (now_tm)); + bcopy (localtime (&tv->tv_sec), &tm, sizeof tm); + + if (width <= 0 || width >= buf_len) + width = buf_len - 1; - strftime (time, sizeof (time), "%r", tv); - if (tm.tm_yday != now_tm.tm_yday || tm.tm_year != now_tm.tm_year) - strftime (day, sizeof (day), "%a", tv); - if (tm.tm_mon != now_tm.tm_mon) - strftime (month, sizeof (month), "%b", tv); + 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 + { + char **fmt, **dfmt, **dfmts; + + if (diff < WEEK) + dfmts = week_fmts; + else if (diff < MONTH) + dfmts = month_fmts; + else if (diff < YEAR) + dfmts = date_fmts; + else + dfmts = year_fmts; + + for (fmt = time_fmts; *fmt && !used; fmt++) + { + for (dfmt = dfmts; *dfmt && !used; dfmt++) + { + char whole_fmt[strlen (*dfmt) + 1 + strlen (*fmt) + 1]; + char *end = whole_fmt; + + end = stpcpy (end, *dfmt); + *end++ = ' '; + end = stpcpy (end, *fmt); + + used = strftime (buf, width, 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; } -#endif |