#include "hmd.h" #include #include #include #include #include #include #include #include #define ABS(x) ((x) > 0 ? (x) : -(x)) #define DAYS(tm) ((tm)->tm_year * 365 + (tm)->tm_yday) #define BETWEEN(x, start, end) (((x) >= (start)) && ((x) < (end))) const char *hmd_wdnames[7] = { [HMD_SUN] = "sunday", [HMD_MON] = "monday", [HMD_TUE] = "tuesday", [HMD_WED] = "wednesday", [HMD_THU] = "thursday", [HMD_FRI] = "friday", [HMD_SAT] = "saturday" }; const char *hmd_mnames[12] = { [HMD_JAN] = "january", [HMD_FEB] = "february", [HMD_MAR] = "march", [HMD_APR] = "april", [HMD_MAY] = "may", [HMD_JUN] = "june", [HMD_JUL] = "july", [HMD_AUG] = "august", [HMD_SEP] = "september", [HMD_OCT] = "october", [HMD_NOV] = "november", [HMD_DEC] = "december" }; const int hmd_mdays[12] = { [HMD_JAN] = 31, [HMD_FEB] = 28, [HMD_MAR] = 31, [HMD_APR] = 30, [HMD_MAY] = 31, [HMD_JUN] = 30, [HMD_JUL] = 31, [HMD_AUG] = 31, [HMD_SEP] = 30, [HMD_OCT] = 31, [HMD_NOV] = 30, [HMD_DEC] = 31, }; static char * strfmt(const struct allocator *allocator, int *rc, const char *fmtstr, ...) { va_list ap, cpy; char *str; ssize_t n; va_copy(cpy, ap); va_start(cpy, fmtstr); n = vsnprintf(NULL, 0, fmtstr, cpy); va_end(cpy); if (n <= 0) { if (rc) *rc = HMD_ERR_INT; return NULL; } str = allocator->alloc(allocator, (size_t) (n + 1), rc); if (!str && rc) *rc = -*rc; if (!str) return NULL; va_start(ap, fmtstr); vsnprintf(str, (size_t) (n + 1), fmtstr, ap); va_end(ap); return str; } static bool has_prefix(const char *text, const char *prefix) { size_t len; len = strlen(prefix); if (len > strlen(text)) return false; return !strncmp(text, prefix, len); } static bool is_prefix(const char *prefix, const char *text, int minlen) { size_t len; len = strlen(prefix); if (len < minlen || len > strlen(text)) return false; return !strncmp(prefix, text, len); } static struct tm startofday(struct tm tm) { tm.tm_hour = 0; tm.tm_min = 0; tm.tm_sec = 0; return tm; } static enum hmd_err tm2time(uint64_t *time, struct tm tm) { time_t res; tm.tm_isdst = -1; /* infer timezone */ res = mktime(&tm); if (res == (time_t) -1) return HMD_ERR_OOB; *time = (uint64_t) res; return HMD_OK; } static enum hmd_err time2tm(struct tm *tm, uint64_t time) { struct tm *res; time_t tmp; tmp = (time_t) time; res = localtime(&tmp); if (!res) return HMD_ERR_OOB; *tm = *res; return HMD_OK; } static char * allocdup(const struct allocator *allocator, int *rc, const char *str) { char *dup; dup = allocator->alloc(allocator, strlen(str) + 1, rc); if (!dup) return NULL; strcpy(dup, str); return dup; } static char * concat(char *buf, char *c, size_t *cap, const struct allocator *allocator, int *rc, const char *fmt, ...) { va_list ap, cpy; size_t left, off; ssize_t n; if (!c) return NULL; va_copy(cpy, ap); off = (size_t) (c - buf); left = *cap - off; va_start(ap, fmt); n = vsnprintf(c, left, fmt, ap); va_end(ap); if (n >= left) { if (off + (size_t) n + 1 > *cap * 2) *cap = off + (size_t) n + 1; else *cap *= 2; buf = allocator->realloc(allocator, buf, *cap, rc); if (!buf && rc) *rc = -*rc; if (!buf) return NULL; c = buf + off; left = *cap - off; va_start(cpy, fmt); n = vsnprintf(c, left, fmt, cpy); va_end(cpy); } if (n < 0 || n > left) { if (rc) *rc = HMD_ERR_INT; return NULL; } return c + n; } enum hmd_err hmd_spn_parse(struct hmd_spn *spn, const char *arg) { const char *c; char *end; uint64_t v; memset(spn, 0, sizeof(struct hmd_spn)); for (c = arg; *c; c = end + 1) { v = strtoul(c, &end, 10); if (!end) return HMD_ERR_FMT; if (v >= UINT32_MAX) return HMD_ERR_FMT; switch (*end) { case 'y': spn->years += (uint32_t) v; break; case 'w': spn->weeks += (uint32_t) v; break; case 'd': spn->days += (uint32_t) v; break; case 'h': spn->hours += (uint32_t) v; break; case 'm': spn->mins += (uint32_t) v; break; case 's': spn->secs += (uint32_t) v; break; default: return HMD_ERR_FMT; } } return HMD_OK; } enum hmd_err hmd_spn_ival_parse(struct hmd_spn *spn, const char *arg) { memset(spn, 0, sizeof(struct hmd_spn)); if (!strcmp(arg, "yearly")) { spn->years = 1; } else if (!strcmp(arg, "weekly")) { spn->weeks = 1; } else if (!strcmp(arg, "daily")) { spn->days = 1; } else if (!strcmp(arg, "hourly")) { spn->hours = 1; } else { return hmd_spn_parse(spn, arg); } return HMD_OK; } enum hmd_err hmd_spn_calc(struct hmd_spn *spn, uint64_t start, uint64_t end) { int years, days, hours, mins, secs; struct tm tm_start, tm_end; enum hmd_err err; if (end < start) return HMD_ERR_ARG; err = time2tm(&tm_start, start); if (err) return err; err = time2tm(&tm_end, end); if (err) return err; years = tm_end.tm_year - tm_start.tm_year; days = tm_end.tm_yday - tm_start.tm_yday; years = days = hours = mins = secs = 0; secs += tm_end.tm_sec - tm_start.tm_sec; if (secs < 0) { mins -= 1; secs += 60; } mins += tm_end.tm_min - tm_start.tm_min; if (mins < 0) { hours -= 1; mins += 60; } hours += tm_end.tm_hour - tm_start.tm_hour; if (hours < 0) { days -= 1; hours += 24; } days += tm_end.tm_yday - tm_start.tm_yday; if (days < 0) { years -= 1; days += 365; } if (years < 0) return HMD_ERR_INT; spn->years = (uint32_t) years; spn->days = (uint32_t) days % 7; spn->weeks = (uint32_t) days / 7; spn->hours = (uint32_t) hours; spn->mins = (uint32_t) mins; spn->secs = (uint32_t) secs; return HMD_OK; } uint64_t hmd_spn_secs(struct hmd_spn *spn) { return spn->years * 365 * 24 * 3600 + (spn->weeks * 7 + spn->days) * 24 * 3600 + spn->hours * 3600 + spn->mins * 60 + spn->secs; } char * hmd_spn_str(struct hmd_spn *spn, const struct allocator *allocator, int *rc) { char *str, *c; size_t cap; cap = 1; c = str = allocator->alloc(allocator, cap, rc); if (!str && rc) *rc = -*rc; if (!str) return NULL; if (spn->years) c = concat(str, c, &cap, allocator, rc, "%uy", spn->years); if (spn->weeks) c = concat(str, c, &cap, allocator, rc, "%uw", spn->weeks); if (spn->days) c = concat(str, c, &cap, allocator, rc, "%ud", spn->days); if (spn->hours) c = concat(str, c, &cap, allocator, rc, "%uh", spn->hours); if (spn->mins) c = concat(str, c, &cap, allocator, rc, "%um", spn->mins); if (spn->secs) c = concat(str, c, &cap, allocator, rc, "%us", spn->secs); if (!c) { allocator->free(allocator, str); return NULL; } *c = '\0'; return str; } char * hmd_spn_ival_str(struct hmd_spn *spn, const struct allocator *allocator, int *rc) { if (!spn->secs && !spn->mins && spn->hours != 0 && !spn->days && !spn->weeks && !spn->years) { return allocdup(allocator, rc, "hourly"); } else if (!spn->secs && !spn->mins && !spn->hours && spn->days != 0 && !spn->weeks && !spn->years) { return allocdup(allocator, rc, "daily"); } else if (!spn->secs && !spn->mins && !spn->hours && !spn->days && spn->weeks != 0 && !spn->years) { return allocdup(allocator, rc, "weekly"); } else if (!spn->secs && !spn->mins && !spn->hours && !spn->days && !spn->weeks && spn->years != 0) { return allocdup(allocator, rc, "yearly"); } else { return hmd_spn_str(spn, allocator, rc); } } enum hmd_err hmd_date_parse_daydate_reltime_weekday(struct hmd_date *date, char *arg) { struct tm datetm, nowtm; enum hmd_err err; time_t now; int i; now = time(NULL); nowtm = *localtime(&now); for (i = 0; i < 7; i++) { if (is_prefix(arg, hmd_wdnames[i], 3)) { datetm = startofday(nowtm); datetm.tm_mday += i - datetm.tm_wday; datetm.tm_mday += (i < datetm.tm_wday ? 7 : 0); break; } else if (has_prefix(arg, "next-") && is_prefix(arg + 5, hmd_wdnames[i], 3)) { datetm = startofday(nowtm); datetm.tm_mday += i - datetm.tm_wday; datetm.tm_mday += (i < datetm.tm_wday ? 7 : 0) + 7; break; } else if (has_prefix(arg, "last-") && is_prefix(arg + 5, hmd_wdnames[i], 3)) { datetm = startofday(nowtm); datetm.tm_mday += i - datetm.tm_wday; datetm.tm_mday += (i < datetm.tm_wday ? 7 : 0) - 7; break; } } if (i == 7) return HMD_ERR_FMT; err = tm2time(&date->ts, datetm); if (err) return err; date->spn = HMD_DAY_SPN; return HMD_OK; } enum hmd_err hmd_date_parse_daydate_reltime_coarse(struct hmd_date *date, char *arg) { time_t now; struct tm datetm, nowtm; uint64_t next; enum hmd_err err; size_t i; now = time(NULL); nowtm = *localtime(&now); if (is_prefix(arg, "tomorrow", 3)) { datetm = startofday(nowtm); datetm.tm_mday += 1; date->spn = HMD_DAY_SPN; err = tm2time(&date->ts, datetm); if (err) return err; } else if (is_prefix(arg, "today", 3)) { datetm = startofday(nowtm); date->spn = HMD_DAY_SPN; err = tm2time(&date->ts, datetm); if (err) return err; } else if (is_prefix(arg, "yesterday", 3)) { datetm = startofday(nowtm); datetm.tm_mday -= 1; date->spn = HMD_DAY_SPN; err = tm2time(&date->ts, datetm); if (err) return err; } else { for (i = 0; i < 12; i++) { if (is_prefix(arg, hmd_mnames[i], 3)) { datetm = startofday(nowtm); datetm.tm_mday = 1; datetm.tm_mon = (uint8_t) i; err = tm2time(&date->ts, datetm); if (err) return err; datetm.tm_mon += 1; err = tm2time(&next, datetm); if (err) return err; date->spn = next - date->ts; break; } } if (i == 12) { err = hmd_date_parse_daydate_reltime_weekday(date, arg); if (err) return err; } } return HMD_OK; } enum hmd_err hmd_date_parse_daydate_iso8601(struct hmd_date *date, char *arg) { struct tm nowtm, tm; int year, month, day; enum hmd_err err; uint64_t next; time_t now; int n; now = time(NULL); nowtm = *localtime(&now); n = 0; if (sscanf(arg, "%u-%u-%u%n", &year, &month, &day, &n) == 3 && !arg[n]) { tm = startofday(nowtm); tm.tm_year = year - 1900; tm.tm_mon = month - 1; tm.tm_mday = day; err = tm2time(&date->ts, tm); if (err) return err; date->spn = HMD_DAY_SPN; } else if (sscanf(arg, "%u-%u%n", &year, &month, &n) == 2 && !arg[n]) { tm = startofday(nowtm); tm.tm_year = year - 1900; tm.tm_mon = month - 1; tm.tm_mday = 1; err = tm2time(&date->ts, tm); if (err) return err; tm.tm_mon++; err = tm2time(&next, tm); if (err) return err; date->spn = next - date->ts; } else if (sscanf(arg, "%uY%n", &year, &n) == 1 && !arg[n]) { tm = startofday(nowtm); tm.tm_year = year - 1900; tm.tm_mon = 0; tm.tm_mday = 1; err = tm2time(&date->ts, tm); if (err) return err; tm.tm_year++; err = tm2time(&next, tm); if (err) return err; date->spn = next - date->ts; } else { return HMD_ERR_FMT; } return HMD_OK; } enum hmd_err hmd_date_parse_daydate(struct hmd_date *date, char *arg) { enum hmd_err err; err = hmd_date_parse_daydate_reltime_coarse(date, arg); if (err) err = hmd_date_parse_daydate_iso8601(date, arg); if (err) return err; return HMD_OK; } enum hmd_err hmd_timeofday_parse(struct hmd_tod *tod, char *arg) { unsigned int hour, min, sec; char end; hour = min = sec = 0; if (sscanf(arg, "%u:%u:%u%c", &hour, &min, &sec, &end) == 3) { tod->spn = HMD_SEC_SPN; } else if (sscanf(arg, "%u:%u%c", &hour, &min, &end) == 2) { tod->spn = HMD_MIN_SPN; } else if (sscanf(arg, "%u%c", &hour, &end) == 1) { tod->spn = HMD_HOUR_SPN; } else { return HMD_ERR_FMT; } if (hour >= 24 || min >= 60 || sec >= 60) return HMD_ERR_OOB; tod->hour = (uint8_t) hour; tod->min = (uint8_t) min; tod->sec = (uint8_t) sec; return HMD_OK; } enum hmd_err hmd_date_parse(struct hmd_date *date, char *arg) { struct hmd_tod tod; char *sep; enum hmd_err err; err = HMD_OK; sep = strchr(arg, '@'); if (sep) *sep = '\0'; err = hmd_date_parse_daydate(date, arg); if (err) goto exit; if (sep) { /* ambiguous due to DST */ err = hmd_timeofday_parse(&tod, sep + 1); if (err) goto exit; date->ts += (uint64_t) tod.hour * HMD_HOUR_SPN + tod.min * HMD_MIN_SPN + tod.sec; date->spn = tod.spn; } exit: if (sep) *sep = '@'; return err; } char * hmd_date_str_reltime_exact(struct hmd_date *date, const struct allocator *allocator, int *rc) { time_t days, hours, min, sec; time_t now, diff; char *datestr; now = time(NULL); diff = (time_t) date->ts - now; days = diff / HMD_DAY_SPN; hours = (diff % HMD_DAY_SPN) / HMD_HOUR_SPN; min = (diff % HMD_HOUR_SPN) / HMD_MIN_SPN; sec = diff % HMD_MIN_SPN; if (days) { datestr = strfmt(allocator, rc, "%i day%s%s", ABS(days), ABS(days) > 1 ? "s" : "", days < 0 ? " ago" : ""); } else if (hours) { datestr = strfmt(allocator, rc, "%i hour%s%s", ABS(hours), ABS(hours) > 1 ? "s" : "" , hours < 0 ? " ago" : ""); } else if (min) { datestr = strfmt(allocator, rc, "%i min%s%s", ABS(min), ABS(min) > 1 ? "s" : "", min < 0 ? " ago" : ""); } else if (sec) { datestr = strfmt(allocator, rc, "%i sec%s%s", ABS(sec), ABS(sec) > 1 ? "s" : "", sec < 0 ? " ago" : ""); } else { datestr = strfmt(allocator, rc, "now"); } return datestr; } char * hmd_date_str_reltime_coarse(struct hmd_date *date, const struct allocator *allocator, int *rc) { struct tm nowtm, datetm; enum hmd_err err; char *datestr; time_t now; now = time(NULL); err = time2tm(&nowtm, (uint64_t) now); if (err && rc) *rc = (int) err; if (err) return NULL; err = time2tm(&datetm, date->ts); if (err && rc) *rc = (int) err; if (err) return NULL; if (DAYS(&datetm) == DAYS(&nowtm) - 1) { datestr = strfmt(allocator, rc, "yesterday"); } else if (DAYS(&datetm) == DAYS(&nowtm) + 1) { datestr = strfmt(allocator, rc, "tomorrow"); } else if (DAYS(&datetm) == DAYS(&nowtm)) { datestr = strfmt(allocator, rc, "today"); } else if (BETWEEN(DAYS(&datetm) - DAYS(&nowtm), -7, 0)) { datestr = strfmt(allocator, rc, "last %s", hmd_wdnames[datetm.tm_wday]); } else if (BETWEEN(DAYS(&datetm) - DAYS(&nowtm), 0, 7)) { datestr = strfmt(allocator, rc, "%s", hmd_wdnames[datetm.tm_wday]); } else if (BETWEEN(DAYS(&datetm) - DAYS(&nowtm), 7, 14)) { datestr = strfmt(allocator, rc, "next %s", hmd_wdnames[datetm.tm_wday]); } else { if (rc) *rc = HMD_ERR_FMT; return NULL; } return datestr; } char * hmd_date_str_iso8601(struct hmd_date *date, const struct allocator *allocator, int *rc) { struct tm tm; enum hmd_err err; size_t len; char *str; err = time2tm(&tm, date->ts); if (err && rc) *rc = (int) err; if (err) return NULL; str = allocator->alloc(allocator, 20, rc); if (!str && rc) *rc = -*rc; if (!str) return NULL; if (date->spn < HMD_MIN_SPN) { len = strftime(str, 21, "%Y-%m-%d %H:%M:%S", &tm); } else if (date->spn >= HMD_MIN_SPN && date->spn < HMD_DAY_SPN) { len = strftime(str, 21, "%Y-%m-%d %H:%M", &tm); } else { len = strftime(str, 21, "%Y-%m-%d", &tm); } if (!len) { if (rc) *rc = HMD_ERR_INT; allocator->free(allocator, str); return NULL; } return str; } char * hmd_date_str(struct hmd_date *date, const struct allocator *allocator, int *rc) { struct tm nowtm, datetm; enum hmd_err err; time_t now; char *str; now = time(NULL); err = time2tm(&nowtm, (uint64_t) now); if (err && rc) *rc = (int) err; if (err) return NULL; err = time2tm(&datetm, date->ts); if (err && rc) *rc = (int) err; if (err) return NULL; if (DAYS(&nowtm) == DAYS(&datetm)) return hmd_date_str_reltime_exact(date, allocator, rc); str = hmd_date_str_reltime_coarse(date, allocator, rc); if (!str) str = hmd_date_str_iso8601(date, allocator, rc); return str; }