libhmd-c

C human-readable date string library
git clone https://git.sinitax.com/sinitax/libhmd-c
Log | Files | Refs | Submodules | LICENSE | sfeed.txt

hmd.c (15541B)


      1#include "hmd.h"
      2
      3#include <stdint.h>
      4#include <time.h>
      5#include <errno.h>
      6#include <stdarg.h>
      7#include <stdio.h>
      8#include <string.h>
      9#include <stdbool.h>
     10#include <stdlib.h>
     11
     12#define ABS(x) ((x) > 0 ? (x) : -(x))
     13#define DAYS(tm) ((tm)->tm_year * 365 + (tm)->tm_yday)
     14#define BETWEEN(x, start, end) (((x) >= (start)) && ((x) < (end)))
     15
     16const char *hmd_wdnames[7] = {
     17	[HMD_SUN] = "sunday",
     18	[HMD_MON] = "monday",
     19	[HMD_TUE] = "tuesday",
     20	[HMD_WED] = "wednesday",
     21	[HMD_THU] = "thursday",
     22	[HMD_FRI] = "friday",
     23	[HMD_SAT] = "saturday"
     24};
     25
     26const char *hmd_mnames[12] = {
     27	[HMD_JAN] = "january",
     28	[HMD_FEB] = "february",
     29	[HMD_MAR] = "march",
     30	[HMD_APR] = "april",
     31	[HMD_MAY] = "may",
     32	[HMD_JUN] = "june",
     33	[HMD_JUL] = "july",
     34	[HMD_AUG] = "august",
     35	[HMD_SEP] = "september",
     36	[HMD_OCT] = "october",
     37	[HMD_NOV] = "november",
     38	[HMD_DEC] = "december"
     39};
     40
     41const int hmd_mdays[12] = {
     42	[HMD_JAN] = 31,
     43	[HMD_FEB] = 28,
     44	[HMD_MAR] = 31,
     45	[HMD_APR] = 30,
     46	[HMD_MAY] = 31,
     47	[HMD_JUN] = 30,
     48	[HMD_JUL] = 31,
     49	[HMD_AUG] = 31,
     50	[HMD_SEP] = 30,
     51	[HMD_OCT] = 31,
     52	[HMD_NOV] = 30,
     53	[HMD_DEC] = 31,
     54};
     55
     56static char *
     57strfmt(const struct allocator *allocator, int *rc, const char *fmtstr, ...)
     58{
     59	va_list ap, cpy;
     60	char *str;
     61	ssize_t n;
     62
     63	va_copy(cpy, ap);
     64
     65	va_start(cpy, fmtstr);
     66	n = vsnprintf(NULL, 0, fmtstr, cpy);
     67	va_end(cpy);
     68
     69	if (n <= 0) {
     70		if (rc) *rc = HMD_ERR_INT;
     71		return NULL;
     72	}
     73
     74	str = allocator->alloc(allocator, (size_t) (n + 1), rc);
     75	if (!str && rc) *rc = -*rc;
     76	if (!str) return NULL;
     77
     78	va_start(ap, fmtstr);
     79	vsnprintf(str, (size_t) (n + 1), fmtstr, ap);
     80	va_end(ap);
     81
     82	return str;
     83}
     84
     85static bool
     86has_prefix(const char *text, const char *prefix)
     87{
     88	size_t len;
     89
     90	len = strlen(prefix);
     91	if (len > strlen(text))
     92		return false;
     93
     94	return !strncmp(text, prefix, len);
     95}
     96
     97static bool
     98is_prefix(const char *prefix, const char *text, int minlen)
     99{
    100	size_t len;
    101
    102	len = strlen(prefix);
    103	if (len < minlen || len > strlen(text))
    104		return false;
    105
    106	return !strncmp(prefix, text, len);
    107}
    108
    109static struct tm
    110startofday(struct tm tm)
    111{
    112	tm.tm_hour = 0;
    113	tm.tm_min = 0;
    114	tm.tm_sec = 0;
    115
    116	return tm;
    117}
    118
    119static enum hmd_err
    120tm2time(uint64_t *time, struct tm tm)
    121{
    122	time_t res;
    123
    124	tm.tm_isdst = -1; /* infer timezone */
    125	res = mktime(&tm);
    126	if (res == (time_t) -1)
    127		return HMD_ERR_OOB;
    128	*time = (uint64_t) res;
    129
    130	return HMD_OK;
    131}
    132
    133static enum hmd_err
    134time2tm(struct tm *tm, uint64_t time)
    135{
    136	struct tm *res;
    137	time_t tmp;
    138
    139	tmp = (time_t) time;
    140	res = localtime(&tmp);
    141	if (!res) return HMD_ERR_OOB;
    142	*tm = *res;
    143
    144	return HMD_OK;
    145}
    146
    147static char *
    148allocdup(const struct allocator *allocator, int *rc, const char *str)
    149{
    150	char *dup;
    151
    152	dup = allocator->alloc(allocator, strlen(str) + 1, rc);
    153	if (!dup) return NULL;
    154
    155	strcpy(dup, str);
    156
    157	return dup;
    158}
    159
    160static char *
    161concat(char *buf, char *c, size_t *cap, const struct allocator *allocator,
    162	int *rc, const char *fmt, ...)
    163{
    164	va_list ap, cpy;
    165	size_t left, off;
    166	ssize_t n;
    167
    168	if (!c) return NULL;
    169
    170	va_copy(cpy, ap);
    171
    172	off = (size_t) (c - buf);
    173	left = *cap - off;
    174	va_start(ap, fmt);
    175	n = vsnprintf(c, left, fmt, ap);
    176	va_end(ap);
    177
    178	if (n >= left) {
    179		if (off + (size_t) n + 1 > *cap * 2)
    180			*cap = off + (size_t) n + 1;
    181		else
    182			*cap *= 2;
    183		buf = allocator->realloc(allocator, buf, *cap, rc);
    184		if (!buf && rc) *rc = -*rc;
    185		if (!buf) return NULL;
    186
    187		c = buf + off;
    188		left = *cap - off;
    189		va_start(cpy, fmt);
    190		n = vsnprintf(c, left, fmt, cpy);
    191		va_end(cpy);
    192	}
    193
    194	if (n < 0 || n > left) {
    195		if (rc) *rc = HMD_ERR_INT;
    196		return NULL;
    197	}
    198
    199	return c + n;
    200}
    201
    202enum hmd_err
    203hmd_spn_parse(struct hmd_spn *spn, const char *arg)
    204{
    205	const char *c;
    206	char *end;
    207	uint64_t v;
    208
    209	memset(spn, 0, sizeof(struct hmd_spn));
    210	for (c = arg; *c; c = end + 1) {
    211		v = strtoul(c, &end, 10);
    212		if (!end) return HMD_ERR_FMT;
    213		if (v >= UINT32_MAX) return HMD_ERR_FMT;
    214		switch (*end) {
    215		case 'y':
    216			spn->years += (uint32_t) v;
    217			break;
    218		case 'w':
    219			spn->weeks += (uint32_t) v;
    220			break;
    221		case 'd':
    222			spn->days += (uint32_t) v;
    223			break;
    224		case 'h':
    225			spn->hours += (uint32_t) v;
    226			break;
    227		case 'm':
    228			spn->mins += (uint32_t) v;
    229			break;
    230		case 's':
    231			spn->secs += (uint32_t) v;
    232			break;
    233		default:
    234			return HMD_ERR_FMT;
    235		}
    236	}
    237
    238	return HMD_OK;
    239}
    240
    241enum hmd_err
    242hmd_spn_ival_parse(struct hmd_spn *spn, const char *arg)
    243{
    244	memset(spn, 0, sizeof(struct hmd_spn));
    245	if (!strcmp(arg, "yearly")) {
    246		spn->years = 1;
    247	} else if (!strcmp(arg, "weekly")) {
    248		spn->weeks = 1;
    249	} else if (!strcmp(arg, "daily")) {
    250		spn->days = 1;
    251	} else if (!strcmp(arg, "hourly")) {
    252		spn->hours = 1;
    253	} else {
    254		return hmd_spn_parse(spn, arg);
    255	}
    256
    257	return HMD_OK;
    258}
    259
    260enum hmd_err
    261hmd_spn_calc(struct hmd_spn *spn, uint64_t start, uint64_t end)
    262{
    263	int years, days, hours, mins, secs;
    264	struct tm tm_start, tm_end;
    265	enum hmd_err err;
    266
    267	if (end < start) return HMD_ERR_ARG;
    268
    269	err = time2tm(&tm_start, start);
    270	if (err) return err;
    271
    272	err = time2tm(&tm_end, end);
    273	if (err) return err;
    274
    275	years = tm_end.tm_year - tm_start.tm_year;
    276	days = tm_end.tm_yday - tm_start.tm_yday;
    277
    278	years = days = hours = mins = secs = 0;
    279	secs += tm_end.tm_sec - tm_start.tm_sec;
    280	if (secs < 0) {
    281		mins -= 1;
    282		secs += 60;
    283	}
    284
    285	mins += tm_end.tm_min - tm_start.tm_min;
    286	if (mins < 0) {
    287		hours -= 1;
    288		mins += 60;
    289	}
    290
    291	hours += tm_end.tm_hour - tm_start.tm_hour;
    292	if (hours < 0) {
    293		days -= 1;
    294		hours += 24;
    295	}
    296
    297	days += tm_end.tm_yday - tm_start.tm_yday;
    298	if (days < 0) {
    299		years -= 1;
    300		days += 365;
    301	}
    302
    303	if (years < 0) return HMD_ERR_INT;
    304
    305	spn->years = (uint32_t) years;
    306	spn->days = (uint32_t) days % 7;
    307	spn->weeks = (uint32_t) days / 7;
    308	spn->hours = (uint32_t) hours;
    309	spn->mins = (uint32_t) mins;
    310	spn->secs = (uint32_t) secs;
    311
    312	return HMD_OK;
    313}
    314
    315uint64_t
    316hmd_spn_secs(struct hmd_spn *spn)
    317{
    318	return spn->years * 365 * 24 * 3600
    319		+ (spn->weeks * 7 + spn->days) * 24 * 3600
    320		+ spn->hours * 3600 + spn->mins * 60 + spn->secs;
    321}
    322
    323char *
    324hmd_spn_str(struct hmd_spn *spn,
    325	const struct allocator *allocator, int *rc)
    326{
    327	char *str, *c;
    328	size_t cap;
    329
    330	cap = 1;
    331	c = str = allocator->alloc(allocator, cap, rc);
    332	if (!str && rc) *rc = -*rc;
    333	if (!str) return NULL;
    334
    335	if (spn->years) c = concat(str, c, &cap, allocator, rc, "%uy", spn->years);
    336	if (spn->weeks) c = concat(str, c, &cap, allocator, rc, "%uw", spn->weeks);
    337	if (spn->days) c = concat(str, c, &cap, allocator, rc, "%ud", spn->days);
    338	if (spn->hours) c = concat(str, c, &cap, allocator, rc, "%uh", spn->hours);
    339	if (spn->mins) c = concat(str, c, &cap, allocator, rc, "%um", spn->mins);
    340	if (spn->secs) c = concat(str, c, &cap, allocator, rc, "%us", spn->secs);
    341	if (!c) {
    342		allocator->free(allocator, str);
    343		return NULL;
    344	}
    345
    346	*c = '\0';
    347
    348	return str;
    349}
    350
    351char *
    352hmd_spn_ival_str(struct hmd_spn *spn,
    353	const struct allocator *allocator, int *rc)
    354{
    355	if (!spn->secs && !spn->mins && spn->hours != 0
    356			&& !spn->days && !spn->weeks && !spn->years) {
    357		return allocdup(allocator, rc, "hourly");
    358	} else if (!spn->secs && !spn->mins && !spn->hours
    359			&& spn->days != 0 && !spn->weeks && !spn->years) {
    360		return allocdup(allocator, rc, "daily");
    361	} else if (!spn->secs && !spn->mins && !spn->hours
    362			&& !spn->days && spn->weeks != 0 && !spn->years) {
    363		return allocdup(allocator, rc, "weekly");
    364	} else if (!spn->secs && !spn->mins && !spn->hours
    365			&& !spn->days && !spn->weeks && spn->years != 0) {
    366		return allocdup(allocator, rc, "yearly");
    367	} else {
    368		return hmd_spn_str(spn, allocator, rc);
    369	}
    370}
    371
    372enum hmd_err
    373hmd_date_parse_daydate_reltime_weekday(struct hmd_date *date, char *arg)
    374{
    375	struct tm datetm, nowtm;
    376	enum hmd_err err;
    377	time_t now;
    378	int i;
    379
    380	now = time(NULL);
    381	nowtm = *localtime(&now);
    382
    383	for (i = 0; i < 7; i++) {
    384		if (is_prefix(arg, hmd_wdnames[i], 3)) {
    385			datetm = startofday(nowtm);
    386			datetm.tm_mday += i - datetm.tm_wday;
    387			datetm.tm_mday += (i < datetm.tm_wday ? 7 : 0);
    388			break;
    389		} else if (has_prefix(arg, "next-")
    390				&& is_prefix(arg + 5, hmd_wdnames[i], 3)) {
    391			datetm = startofday(nowtm);
    392			datetm.tm_mday += i - datetm.tm_wday;
    393			datetm.tm_mday += (i < datetm.tm_wday ? 7 : 0) + 7;
    394			break;
    395		} else if (has_prefix(arg, "last-")
    396				&& is_prefix(arg + 5, hmd_wdnames[i], 3)) {
    397			datetm = startofday(nowtm);
    398			datetm.tm_mday += i - datetm.tm_wday;
    399			datetm.tm_mday += (i < datetm.tm_wday ? 7 : 0) - 7;
    400			break;
    401		}
    402	}
    403
    404	if (i == 7) return HMD_ERR_FMT;
    405
    406	err = tm2time(&date->ts, datetm);
    407	if (err) return err;
    408
    409	date->spn = HMD_DAY_SPN;
    410
    411	return HMD_OK;
    412}
    413
    414enum hmd_err
    415hmd_date_parse_daydate_reltime_coarse(struct hmd_date *date, char *arg)
    416{
    417	time_t now;
    418	struct tm datetm, nowtm;
    419	uint64_t next;
    420	enum hmd_err err;
    421	size_t i;
    422
    423	now = time(NULL);
    424	nowtm = *localtime(&now);
    425
    426	if (is_prefix(arg, "tomorrow", 3)) {
    427		datetm = startofday(nowtm);
    428		datetm.tm_mday += 1;
    429		date->spn = HMD_DAY_SPN;
    430		err = tm2time(&date->ts, datetm);
    431		if (err) return err;
    432	} else if (is_prefix(arg, "today", 3)) {
    433		datetm = startofday(nowtm);
    434		date->spn = HMD_DAY_SPN;
    435		err = tm2time(&date->ts, datetm);
    436		if (err) return err;
    437	} else if (is_prefix(arg, "yesterday", 3)) {
    438		datetm = startofday(nowtm);
    439		datetm.tm_mday -= 1;
    440		date->spn = HMD_DAY_SPN;
    441		err = tm2time(&date->ts, datetm);
    442		if (err) return err;
    443	} else {
    444		for (i = 0; i < 12; i++) {
    445			if (is_prefix(arg, hmd_mnames[i], 3)) {
    446				datetm = startofday(nowtm);
    447				datetm.tm_mday = 1;
    448				datetm.tm_mon = (uint8_t) i;
    449				err = tm2time(&date->ts, datetm);
    450				if (err) return err;
    451				datetm.tm_mon += 1;
    452				err = tm2time(&next, datetm);
    453				if (err) return err;
    454				date->spn = next - date->ts;
    455				break;
    456			}
    457		}
    458
    459		if (i == 12) {
    460			err = hmd_date_parse_daydate_reltime_weekday(date, arg);
    461			if (err) return err;
    462		}
    463	}
    464
    465	return HMD_OK;
    466}
    467
    468enum hmd_err
    469hmd_date_parse_daydate_iso8601(struct hmd_date *date, char *arg)
    470{
    471	struct tm nowtm, tm;
    472	int year, month, day;
    473	enum hmd_err err;
    474	uint64_t next;
    475	time_t now;
    476	int n;
    477
    478	now = time(NULL);
    479	nowtm = *localtime(&now);
    480
    481	n = 0;
    482	if (sscanf(arg, "%u-%u-%u%n", &year, &month, &day, &n) == 3 && !arg[n]) {
    483		tm = startofday(nowtm);
    484		tm.tm_year = year - 1900;
    485		tm.tm_mon = month - 1;
    486		tm.tm_mday = day;
    487		err = tm2time(&date->ts, tm);
    488		if (err) return err;
    489		date->spn = HMD_DAY_SPN;
    490	} else if (sscanf(arg, "%u-%u%n", &year, &month, &n) == 2 && !arg[n]) {
    491		tm = startofday(nowtm);
    492		tm.tm_year = year - 1900;
    493		tm.tm_mon = month - 1;
    494		tm.tm_mday = 1;
    495		err = tm2time(&date->ts, tm);
    496		if (err) return err;
    497		tm.tm_mon++;
    498		err = tm2time(&next, tm);
    499		if (err) return err;
    500		date->spn = next - date->ts;
    501	} else if (sscanf(arg, "%uY%n", &year, &n) == 1 && !arg[n]) {
    502		tm = startofday(nowtm);
    503		tm.tm_year = year - 1900;
    504		tm.tm_mon = 0;
    505		tm.tm_mday = 1;
    506		err = tm2time(&date->ts, tm);
    507		if (err) return err;
    508		tm.tm_year++;
    509		err = tm2time(&next, tm);
    510		if (err) return err;
    511		date->spn = next - date->ts;
    512	} else {
    513		return HMD_ERR_FMT;
    514	}
    515
    516	return HMD_OK;
    517}
    518
    519enum hmd_err
    520hmd_date_parse_daydate(struct hmd_date *date, char *arg)
    521{
    522	enum hmd_err err;
    523
    524	err = hmd_date_parse_daydate_reltime_coarse(date, arg);
    525	if (err) err = hmd_date_parse_daydate_iso8601(date, arg);
    526	if (err) return err;
    527
    528	return HMD_OK;
    529}
    530
    531enum hmd_err
    532hmd_timeofday_parse(struct hmd_tod *tod, char *arg)
    533{
    534	unsigned int hour, min, sec;
    535	char end;
    536
    537	hour = min = sec = 0;
    538	if (sscanf(arg, "%u:%u:%u%c", &hour, &min, &sec, &end) == 3) {
    539		tod->spn = HMD_SEC_SPN;
    540	} else if (sscanf(arg, "%u:%u%c", &hour, &min, &end) == 2) {
    541		tod->spn = HMD_MIN_SPN;
    542	} else if (sscanf(arg, "%u%c", &hour, &end) == 1) {
    543		tod->spn = HMD_HOUR_SPN;
    544	} else {
    545		return HMD_ERR_FMT;
    546	}
    547
    548	if (hour >= 24 || min >= 60 || sec >= 60)
    549		return HMD_ERR_OOB;
    550
    551	tod->hour = (uint8_t) hour;
    552	tod->min = (uint8_t) min;
    553	tod->sec = (uint8_t) sec;
    554
    555	return HMD_OK;
    556}
    557
    558enum hmd_err
    559hmd_date_parse(struct hmd_date *date, char *arg)
    560{
    561	struct hmd_tod tod;
    562	char *sep;
    563	enum hmd_err err;
    564
    565	err = HMD_OK;
    566	sep = strchr(arg, '@');
    567	if (sep) *sep = '\0';
    568
    569	err = hmd_date_parse_daydate(date, arg);
    570	if (err) goto exit;
    571
    572	if (sep) {
    573		/* ambiguous due to DST */
    574		err = hmd_timeofday_parse(&tod, sep + 1);
    575		if (err) goto exit;
    576
    577		date->ts += (uint64_t) tod.hour * HMD_HOUR_SPN
    578			+ tod.min * HMD_MIN_SPN + tod.sec;
    579		date->spn = tod.spn;
    580	}
    581
    582exit:
    583	if (sep) *sep = '@';
    584	return err;
    585}
    586
    587char *
    588hmd_date_str_reltime_exact(struct hmd_date *date,
    589	const struct allocator *allocator, int *rc)
    590{
    591	time_t days, hours, min, sec;
    592	time_t now, diff;
    593	char *datestr;
    594
    595	now = time(NULL);
    596	diff = (time_t) date->ts - now;
    597
    598	days = diff / HMD_DAY_SPN;
    599	hours = (diff % HMD_DAY_SPN) / HMD_HOUR_SPN;
    600	min = (diff % HMD_HOUR_SPN) / HMD_MIN_SPN;
    601	sec = diff % HMD_MIN_SPN;
    602
    603	if (days) {
    604		datestr = strfmt(allocator, rc, "%i day%s%s", ABS(days),
    605			ABS(days) > 1 ? "s" : "", days < 0 ? " ago" : "");
    606	} else if (hours) {
    607		datestr = strfmt(allocator, rc, "%i hour%s%s", ABS(hours),
    608			ABS(hours) > 1 ? "s" : "" , hours < 0 ? " ago" : "");
    609	} else if (min) {
    610		datestr = strfmt(allocator, rc, "%i min%s%s", ABS(min),
    611			ABS(min) > 1 ? "s" : "", min < 0 ? " ago" : "");
    612	} else if (sec) {
    613		datestr = strfmt(allocator, rc, "%i sec%s%s", ABS(sec),
    614			ABS(sec) > 1 ? "s" : "", sec < 0 ? " ago" : "");
    615	} else {
    616		datestr = strfmt(allocator, rc, "now");
    617	}
    618
    619	return datestr;
    620}
    621
    622char *
    623hmd_date_str_reltime_coarse(struct hmd_date *date,
    624	const struct allocator *allocator, int *rc)
    625{
    626	struct tm nowtm, datetm;
    627	enum hmd_err err;
    628	char *datestr;
    629	time_t now;
    630
    631	now = time(NULL);
    632
    633	err = time2tm(&nowtm, (uint64_t) now);
    634	if (err && rc) *rc = (int) err;
    635	if (err) return NULL;
    636
    637	err = time2tm(&datetm, date->ts);
    638	if (err && rc) *rc = (int) err;
    639	if (err) return NULL;
    640
    641	if (DAYS(&datetm) == DAYS(&nowtm) - 1) {
    642		datestr = strfmt(allocator, rc, "yesterday");
    643	} else if (DAYS(&datetm) == DAYS(&nowtm) + 1) {
    644		datestr = strfmt(allocator, rc, "tomorrow");
    645	} else if (DAYS(&datetm) == DAYS(&nowtm)) {
    646		datestr = strfmt(allocator, rc, "today");
    647	} else if (BETWEEN(DAYS(&datetm) - DAYS(&nowtm), -7, 0)) {
    648		datestr = strfmt(allocator, rc,
    649			"last %s", hmd_wdnames[datetm.tm_wday]);
    650	} else if (BETWEEN(DAYS(&datetm) - DAYS(&nowtm), 0, 7)) {
    651		datestr = strfmt(allocator, rc,
    652			"%s", hmd_wdnames[datetm.tm_wday]);
    653	} else if (BETWEEN(DAYS(&datetm) - DAYS(&nowtm), 7, 14)) {
    654		datestr = strfmt(allocator, rc,
    655			"next %s", hmd_wdnames[datetm.tm_wday]);
    656	} else {
    657		if (rc) *rc = HMD_ERR_FMT;
    658		return NULL;
    659	}
    660
    661	return datestr;
    662}
    663
    664char *
    665hmd_date_str_iso8601(struct hmd_date *date,
    666	const struct allocator *allocator, int *rc)
    667{
    668	struct tm tm;
    669	enum hmd_err err;
    670	size_t len;
    671	char *str;
    672
    673	err = time2tm(&tm, date->ts);
    674	if (err && rc) *rc = (int) err;
    675	if (err) return NULL;
    676
    677	str = allocator->alloc(allocator, 20, rc);
    678	if (!str && rc) *rc = -*rc;
    679	if (!str) return NULL;
    680
    681	if (date->spn < HMD_MIN_SPN) {
    682		len = strftime(str, 21, "%Y-%m-%d %H:%M:%S", &tm);
    683	} else if (date->spn >= HMD_MIN_SPN && date->spn < HMD_DAY_SPN) {
    684		len = strftime(str, 21, "%Y-%m-%d %H:%M", &tm);
    685	} else {
    686		len = strftime(str, 21, "%Y-%m-%d", &tm);
    687	}
    688
    689	if (!len) {
    690		if (rc) *rc = HMD_ERR_INT;
    691		allocator->free(allocator, str);
    692		return NULL;
    693	}
    694
    695	return str;
    696}
    697
    698char *
    699hmd_date_str(struct hmd_date *date,
    700	const struct allocator *allocator, int *rc)
    701{
    702	struct tm nowtm, datetm;
    703	enum hmd_err err;
    704	time_t now;
    705	char *str;
    706
    707	now = time(NULL);
    708
    709	err = time2tm(&nowtm, (uint64_t) now);
    710	if (err && rc) *rc = (int) err;
    711	if (err) return NULL;
    712
    713	err = time2tm(&datetm, date->ts);
    714	if (err && rc) *rc = (int) err;
    715	if (err) return NULL;
    716
    717	if (DAYS(&nowtm) == DAYS(&datetm))
    718		return hmd_date_str_reltime_exact(date, allocator, rc);
    719
    720	str = hmd_date_str_reltime_coarse(date, allocator, rc);
    721	if (!str) str = hmd_date_str_iso8601(date, allocator, rc);
    722
    723	return str;
    724}
    725