aboutsummaryrefslogblamecommitdiffstats
path: root/fmt.c
blob: 7b73ed7fe5cdb770e33cb1a3c20180f114425285 (plain) (tree)




























































































































































































                                                                                                                                   















                                                                                                     
/* See LICENSE file for copyright and license details. */
#include "common.h"


static const char *units_big = "KMGTPEZYRQ";
static const char *units_small = "munpfazyrq"; /* 'u' should be "µ" */


const char *
humansize1000(off_t s, char *buf)
{
	size_t unit = 0;
	if (s < 1000) {
		sprintf(buf, "%u B", (unsigned)s);
		return buf;
	}
	s /= 100;
	while (units_big[unit + 1U] && s >= 10000) {
		s /= 1000;
		unit++;
	}
	sprintf(buf, "%u.%u %cB", (unsigned)s / 10U, (unsigned)s % 10U, units_big[unit]);
	return buf;
}


const char *
humansize1024(off_t s, char *buf)
{
	size_t unit = 0;
	if (s < 1024) {
		sprintf(buf, "%u B", (unsigned)s);
		return buf;
	}
	while (units_big[unit + 1U] && s >= 1024 * 1024) {
		s /= 1024;
		unit++;
	}
	sprintf(buf, "%lu.%lu %ciB", (unsigned long int)s / 1024UL, (unsigned long int)(s * 10 % 10240) / 1024UL, units_big[unit]);
	return buf;
}


off_t
unhumansize(const char *s, char flag)
{
	off_t sum = 0, term, digit, divisor, power, base;

	if (!isdigit(s[0]) && !(s[0] == '-' && isdigit(s[1])))
		eprintf("value of -%c is not a file size", flag);

	do {
		divisor = 1;
		term = 0;
		while (isdigit(*s)) {
			digit = (*s++ & 15);
			if (term > (OFF_MAX - digit) / 10)
				eprintf("value of -%c option is too large", flag);
			term = term * 10 + digit;
		}
		if (*s == '.') {
			s++;
			while (isdigit(*s)) {
				digit = (*s++ & 15);
				if (term > (OFF_MAX - digit) / 10)
					eprintf("value of -%c option is too large", flag);
				term = term * 10 + digit;
				divisor *= 10;
			}
		}

		power = 0;
		switch (*s) {
		case 'Q': power++; /* fall through */
		case 'R': power++; /* fall through */
		case 'Y': power++; /* fall through */
		case 'Z': power++; /* fall through */
		case 'E': power++; /* fall through */
		case 'P': power++; /* fall through */
		case 'T': power++; /* fall through */
		case 'G': power++; /* fall through */
		case 'M': power++; /* fall through */
		case 'k': case 'K': power++;
			if (s[1] == 'i' || s[2] == 'B') {
				base = 1024;
				s = &s[3];
			} else if (s[1] == 'B') {
				base = 1000;
				s = &s[2];
			} else {
				base = 1024;
				s = &s[1];
			}
			while (power) {
				term *= base;
				power--;
			}
			break;
		case 'B':
			s++;
			/* fall through */
		default:
			if (divisor > 1)
				eprintf("value of -%c contains non-integer exact byte count", flag);
			break;
		}
		sum += term /= divisor;

		while (*s == ' ' || *s == ',' || *s == '+')
			s++;

	} while (isdigit(s[0]) || (s[0] == '.' && isdigit(s[1])));

	return sum;
}


const char *
durationstr(const struct timespec *dur, char *buf, int second_decimals)
{
	uintmax_t ss, s, m, h, d, ss_div = (uintmax_t)WHOLE_SECOND;
	char *p;
	const char *unit;
	int i;

	if (dur->tv_sec < 0 || dur->tv_nsec < 0)
		return "-";

	if (second_decimals < 0)
		second_decimals = 0;
	else if (second_decimals > 9)
		second_decimals = 9;

	for (i = 0; i < second_decimals; i++)
		ss_div /= 10U;
	ss = (uintmax_t)dur->tv_nsec / ss_div;
	s = (uintmax_t)dur->tv_sec % 60U;
	m = (uintmax_t)dur->tv_sec / 60U % 60U;
	h = (uintmax_t)dur->tv_sec / 60U / 60U % 24U;
	d = (uintmax_t)dur->tv_sec / 60U / 60U / 24U;

	p = buf;
	if (d)
		p += sprintf(p, "%ju days, ", d);
	if (h) {
		p += sprintf(p, "%ju:%02ju:%02ju", h, m, s);
		unit = "hours";
	} else if (m) {
		p += sprintf(p, "%ju:%02ju", m, s);
		unit = "minutes";
	} else {
		p += sprintf(p, "%ju", s);
		unit = "seconds";
	}
	if (second_decimals)
		p += sprintf(p, ".%0*ju", second_decimals, ss);
	p += sprintf(p, " %s", unit);

	return buf;
}


const char *
humanbytespersecond(double bytes_per_second, char *buf)
{
	int unit = -1;

	if (bytes_per_second < (double)0.01f) {
		do {
			bytes_per_second *= 1000;
			unit++;
		} while (units_small[unit + 1] && bytes_per_second < (double)0.01f);
		if (units_small[unit] == 'u' && have_micro_symbol())
			sprintf(buf, "%.02lf µB/s", bytes_per_second);
		else
			sprintf(buf, "%.02lf %cB/s", bytes_per_second, units_small[unit]);
	} else {
		while (units_big[unit + 1] && bytes_per_second >= 1000) {
			bytes_per_second /= 1000;
			unit++;
		}
		if (unit < 0)
			sprintf(buf, "%.02lf B/s", bytes_per_second);
		else
			sprintf(buf, "%.02lf %cB/s", bytes_per_second, units_big[unit]);
	}

	return buf;
}


const char *
exact_and_human_size(off_t bytes, char *buf, int with_unit)
{
	char *p = buf;
	p += sprintf(p, "%ji%s", (intmax_t)bytes, !with_unit ? "" : bytes == 1 ? " byte" : " bytes");
	if (bytes >= 1024) {
		p = stpcpy(p, " (");
		humansize1000(bytes, p);
		p = stpcpy(strchr(p, '\0'), ", ");
		humansize1024(bytes, p);
		p = stpcpy(strchr(p, '\0'), ")");
	}
	return buf;
}