commit 3433153426fe443a551a4bd1104959058e111858
Author: Louis Burda <quent.burda@gmail.com>
Date: Mon, 5 Jun 2023 18:38:46 +0200
Add initial version with _parse and _str api
Diffstat:
10 files changed, 734 insertions(+), 0 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -0,0 +1,4 @@
+.cache
+build
+build.jst
+compile_commands.json
diff --git a/.gitmodules b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "lib/liballoc"]
+ path = lib/liballoc
+ url = git@sinitax.com:sinitax/liballoc
diff --git a/build.jst.tmpl b/build.jst.tmpl
@@ -0,0 +1,69 @@
+#default PREFIX /usr/local
+#default INCLDIR /include
+#default LIBDIR /lib
+#default CC gcc
+
+#ifdef libhmd_DEBUG
+#define DEBUG 1
+#endif
+
+#ifdef DEBUG
+#define OPT_CFLAGS -Og -g
+#else
+#define OPT_CFLAGS -O2
+#endif
+
+cflags = -Wunused-function -Wunused-variable -Wconversion -Wformat
+ -I include -I lib/liballoc/include -fPIC
+ #{OPT_CFLAGS} #{libhmd_EXTRA_CFLAGS} #{EXTRA_CFLAGS}
+
+rule liba
+ #{CC} -o $out.tmp.o $in $cflags -r
+ objcopy --keep-global-symbols=libhmd.api $out.tmp.o $out.fixed.o
+ ar rcs $out $out.fixed.o
+ rm $out.tmp.o $out.fixed.o
+
+rule libso
+ #{CC} -o $out $in $cflags -shared -Wl,-version-script libhmd.lds
+
+rule cc
+ #{CC} -o $out $in $cflags
+
+rule mkdir
+ mkdir $out
+
+target build
+ mkdir
+
+target lib/liballoc/build/liballoc.a
+ just lib/liballoc
+
+target build/libhmd.a
+ liba src/hmd.c | include/hmd.h build
+
+target build/libhmd.so
+ libso src/hmd.c | include/hmd.h build
+
+target build/test
+ cc src/test.c build/libhmd.a lib/liballoc/build/liballoc.a | build
+
+command clean
+ rm -rf build
+
+command cleanall
+ just clean
+ just -C lib/liballoc cleanall
+
+command install
+ install -m755 build/libhmd.a -t "#{DESTDIR}#{PREFIX}#{LIBDIR}"
+ install -m755 build/libhmd.so -t "#{DESTDIR}#{PREFIX}#{LIBDIR}"
+ install -m644 include/hmd.h -t "#{DESTDIR}#{PREFIX}#{INCLDIR}"
+
+command uninstall
+ rm -f "#{DESTDIR}#{PREFIX}#{LIBDIR}/libhmd.a"
+ rm -f "#{DESTDIR}#{PREFIX}#{LIBDIR}/libhmd.so"
+ rm -f "#{DESTDIR}#{PREFIX}#{INCLDIR}/hmd.h"
+
+command all
+ just build/libhmd.a build/libhmd.so build/test
+
diff --git a/configure b/configure
@@ -0,0 +1,10 @@
+#!/bin/sh
+
+tmpl "$@" build.jst.tmpl > build.jst
+if [ "$1" != "shallow" ]; then
+ for lib in ./lib/*; do
+ pushd $lib
+ ./configure "$@"
+ popd
+ done
+fi
diff --git a/include/hmd.h b/include/hmd.h
@@ -0,0 +1,91 @@
+#pragma once
+
+#include "allocator.h"
+
+#include <stdint.h>
+
+#define HMD_SEC_SPN (1)
+#define HMD_MIN_SPN (60)
+#define HMD_HOUR_SPN (HMD_MIN_SPN * 60)
+#define HMD_DAY_SPN (HMD_HOUR_SPN * 24)
+#define HMD_WEEK_SPN (HMD_DAY_SPN * 7)
+
+enum hmd_err {
+ HMD_OK,
+ HMD_ERR_OOB,
+ HMD_ERR_FMT,
+ HMD_ERR_SYS,
+ HMD_ERR_INT
+};
+
+enum hmd_mon {
+ HMD_JAN,
+ HMD_FEB,
+ HMD_MAR,
+ HMD_APR,
+ HMD_MAY,
+ HMD_JUN,
+ HMD_JUL,
+ HMD_AUG,
+ HMD_SEP,
+ HMD_OCT,
+ HMD_NOV,
+ HMD_DEC
+};
+
+enum hmd_wday {
+ HMD_SUN,
+ HMD_MON,
+ HMD_TUE,
+ HMD_WED,
+ HMD_THU,
+ HMD_FRI,
+ HMD_SAT,
+};
+
+struct hmd_date {
+ uint64_t ts; /* unix epoch */
+ uint64_t spn;
+};
+
+struct hmd_dd {
+ uint16_t year;
+ uint8_t month;
+ uint8_t day;
+};
+
+struct hmd_tod {
+ uint8_t hour;
+ uint8_t min;
+ uint8_t sec;
+ uint16_t spn;
+};
+
+struct hmd_ival {
+ uint16_t year;
+ uint16_t day;
+ uint8_t hour;
+ uint8_t min;
+ uint8_t sec;
+};
+
+enum hmd_err hmd_date_parse_daydate_reltime_weekday(
+ struct hmd_date *date, char *arg);
+enum hmd_err hmd_date_parse_daydate_reltime_coarse(
+ struct hmd_date *date, char *arg);
+enum hmd_err hmd_date_parse_daydate_iso8601(struct hmd_date *date, char *arg);
+enum hmd_err hmd_timeofday_parse(struct hmd_tod *tod, char *arg);
+enum hmd_err hmd_date_parse(struct hmd_date *date, char *arg);
+
+char *hmd_date_str_reltime_exact(struct hmd_date *date,
+ const struct allocator *allocator, int *rc);
+char *hmd_date_str_reltime_coarse(struct hmd_date *date,
+ const struct allocator *allocator, int *rc);
+char *hmd_date_str_iso8601(struct hmd_date *date,
+ const struct allocator *allocator, int *rc);
+char *hmd_date_str(struct hmd_date *date,
+ const struct allocator *allocator, int *rc);
+
+extern const char *hmd_wdnames[7];
+extern const char *hmd_mnames[12];
+extern const int hmd_mdays[12];
diff --git a/lib/liballoc b/lib/liballoc
@@ -0,0 +1 @@
+Subproject commit 3f388a2659ae2d121322101930d33412815d84e6
diff --git a/libhmd.api b/libhmd.api
@@ -0,0 +1,14 @@
+hmd_date_parse_daydate_reltime_weekday
+hmd_date_parse_daydate_reltime_coarse
+hmd_date_parse_daydate_iso8601
+hmd_timeofday_parse
+hmd_date_parse
+
+hmd_date_str_reltime_exact
+hmd_date_str_reltime_coarse
+hmd_date_str_iso8601
+hmd_date_str
+
+hmd_wdnames
+hmd_mnames
+hmd_mdays
diff --git a/libhmd.lds b/libhmd.lds
@@ -0,0 +1,16 @@
+LIBHMD_0.1.0 {
+ hmd_date_parse_daydate_reltime_weekday;
+ hmd_date_parse_daydate_reltime_coarse;
+ hmd_date_parse_daydate_iso8601;
+ hmd_timeofday_parse;
+ hmd_date_parse;
+
+ hmd_date_str_reltime_exact;
+ hmd_date_str_reltime_coarse;
+ hmd_date_str_iso8601;
+ hmd_date_str;
+
+ hmd_wdnames;
+ hmd_mnames;
+ hmd_mdays;
+};
diff --git a/src/hmd.c b/src/hmd.c
@@ -0,0 +1,509 @@
+#include "hmd.h"
+
+#include <asm-generic/errno.h>
+#include <time.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdbool.h>
+#include <stdlib.h>
+
+#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;
+}
+
+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;
+ char end;
+
+ now = time(NULL);
+ nowtm = *localtime(&now);
+
+ if (sscanf(arg, "%u-%u-%u%c", &year, &month, &day, &end) == 3) {
+ 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%c", &year, &month, &end) == 2) {
+ 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, "%u%c", &year, &end) == 1) {
+ 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 = (int) err;
+ return NULL;
+ }
+
+ err = time2tm(&datetm, date->ts);
+ if (err) {
+ *rc = (int) 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 {
+ *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 = (int) 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 = (int) err;
+ return NULL;
+ }
+
+ err = time2tm(&datetm, date->ts);
+ if (err) {
+ *rc = (int) 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;
+}
+
diff --git a/src/test.c b/src/test.c
@@ -0,0 +1,17 @@
+#include "hmd.h"
+
+#include <err.h>
+#include <stdio.h>
+
+int
+main(int argc, char **argv)
+{
+ struct hmd_date date;
+ char **arg;
+
+ for (arg = argv + 1; *arg; arg++) {
+ if (hmd_date_parse(&date, *arg))
+ err(1, "invalid date '%s'", *arg);
+ printf("%lu %lu\n", date.ts, date.spn);
+ }
+}