aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorChloe Kudryavtsev <code@toast.bunkerlabs.net>2023-05-12 14:53:20 -0400
committerChloe Kudryavtsev <code@toast.bunkerlabs.net>2023-05-12 14:53:20 -0400
commit78e775a1b89c827ad2e96b73a723c7caf83f5a82 (patch)
tree11baea453275a2c9d1d22a64bcbdb9344be0cfb7 /src
parentupdate many things (diff)
rewrite
Split it into multiple things. The new tests will actually reflect how you should use the native library.
Diffstat (limited to 'src')
-rw-r--r--src/date.h28
-rw-r--r--src/main.c6
-rw-r--r--src/polyfill.c19
-rw-r--r--src/polyfill.h22
-rw-r--r--src/time.c100
-rw-r--r--src/tm.c164
-rw-r--r--src/util.c58
7 files changed, 397 insertions, 0 deletions
diff --git a/src/date.h b/src/date.h
new file mode 100644
index 0000000..de0eb2b
--- /dev/null
+++ b/src/date.h
@@ -0,0 +1,28 @@
+#pragma once
+#include <janet.h>
+#include <time.h>
+#include "polyfill.h"
+
+// util.c
+JanetBuffer *strftime_buffer(const char *format, const struct tm *tm, JanetBuffer *buffer);
+struct tm *jd_tm_from_dict(JanetDictView dict);
+JanetTable *jd_tm_to_table(struct tm *tm);
+
+// time.c
+extern const JanetRegExt jd_time_cfuns[];
+time_t *jd_gettime(Janet *argv, int32_t n);
+time_t *jd_maketime(void);
+JANET_CFUN(jd_dict_time);
+JANET_CFUN(jd_gmtime);
+JANET_CFUN(jd_localtime);
+JANET_CFUN(jd_time);
+
+// tm.c
+extern const JanetRegExt jd_tm_cfuns[];
+struct tm *jd_gettm(Janet *argv, int32_t n);
+struct tm *jd_maketm(void);
+JANET_CFUN(jd_dict_tm);
+JANET_CFUN(jd_mktime);
+JANET_CFUN(jd_mktime_inplace);
+JANET_CFUN(jd_strftime);
+JANET_CFUN(jd_tm_dict);
diff --git a/src/main.c b/src/main.c
new file mode 100644
index 0000000..3626c24
--- /dev/null
+++ b/src/main.c
@@ -0,0 +1,6 @@
+#include "date.h"
+
+JANET_MODULE_ENTRY(JanetTable *env) {
+ janet_cfuns_ext(env, "date/native", jd_time_cfuns);
+ janet_cfuns_ext(env, "date/native", jd_tm_cfuns);
+}
diff --git a/src/polyfill.c b/src/polyfill.c
new file mode 100644
index 0000000..0d99916
--- /dev/null
+++ b/src/polyfill.c
@@ -0,0 +1,19 @@
+#include "date.h"
+
+#ifdef POLYFILL_CBYTES
+const char* janet_getcbytes(const Janet *argv, int32_t n) {
+ JanetByteView view = janet_getbytes(argv, n);
+ const char *cstr = (const char *)view.bytes;
+ if (strlen(cstr) != (size_t) view.len) {
+ janet_panic("bytes contain embedded 0s");
+ }
+ return cstr;
+}
+
+const char *janet_optcbytes(const Janet *argv, int32_t argc, int32_t n, const char *dflt) {
+ if (n >= argc || janet_checktype(argv[n], JANET_NIL)) {
+ return dflt;
+ }
+ return janet_getcbytes(argv, n);
+}
+#endif
diff --git a/src/polyfill.h b/src/polyfill.h
new file mode 100644
index 0000000..5fa72ac
--- /dev/null
+++ b/src/polyfill.h
@@ -0,0 +1,22 @@
+#pragma once
+#include <janet.h>
+
+#if JANET_VERSION_MAJOR < 2 && JANET_VERSION_MINOR < 28
+#define POLYFILL_CBYTES
+const char* janet_getcbytes(const Janet *argv, int32_t n);
+const char *janet_optcbytes(const Janet *argv, int32_t argc, int32_t n, const char *dflt);
+
+#if !defined(JANET_NO_SOURCEMAPS) && !defined(JANET_NO_DOCSTRINGS)
+#undef JANET_FN
+#define JANET_FN(CNAME, USAGE, DOCSTRING) \
+ static const int32_t CNAME##_sourceline_ = __LINE__; \
+ static const char CNAME##_docstring_[] = USAGE "\n\n" DOCSTRING; \
+ Janet CNAME (int32_t argc, Janet *argv)
+#elif !defined(JANET_NO_SOURCEMAPS) && defined(JANET_NO_DOCSTRINGS)
+#undef JANET_FN
+#define JANET_FN(CNAME, USAGE, DOCSTRING) \
+ static const int32_t CNAME##_sourceline_ = __LINE__; \
+ Janet CNAME (int32_t argc, Janet *argv)
+#endif // !defined(JANET_NO_SOURCEMAPS)
+
+#endif // JANET_VERSION_MAJOR < 2 && JANET_VERSION_MINOR < 28
diff --git a/src/time.c b/src/time.c
new file mode 100644
index 0000000..64096c7
--- /dev/null
+++ b/src/time.c
@@ -0,0 +1,100 @@
+#include "date.h"
+#include "janet.h"
+
+// wrappers around time_t
+
+static JanetMethod jd_time_methods[] = {
+ {"gmtime", jd_gmtime},
+ {"localtime", jd_localtime},
+ {"todict", jd_dict_time},
+ {NULL, NULL},
+};
+
+static int jd_time_compare(void *lhs, void *rhs) {
+ time_t lhv = (*(time_t*)lhs);
+ time_t rhv = (*(time_t*)rhs);
+ return difftime(lhv, rhv);
+}
+
+static int jd_time_get(void *p, Janet key, Janet *out) {
+ (void) p;
+ if (!janet_checktype(key, JANET_KEYWORD)) {
+ return 0;
+ }
+ return janet_getmethod(janet_unwrap_keyword(key), jd_time_methods, out);
+}
+
+// time_t is always a UTC-representation
+static void jd_time_tostring(void *p, JanetBuffer *buffer) {
+ strftime_buffer("%F %T.000 UTC", localtime(p), buffer);
+}
+
+static const JanetAbstractType jd_time_t = {
+ "time",
+ NULL,
+ NULL,
+ jd_time_get,
+ NULL,
+ NULL,
+ NULL,
+ jd_time_tostring,
+ jd_time_compare,
+ JANET_ATEND_COMPARE
+};
+
+time_t *jd_gettime(Janet *argv, int32_t n) {
+ return (time_t*)janet_getabstract(argv, n, &jd_time_t);
+}
+
+time_t *jd_maketime(void) {
+ return janet_abstract(&jd_time_t, sizeof(time_t));
+}
+
+JANET_FN(jd_dict_time,
+ "(dict->time {...})",
+ "") {
+ janet_fixarity(argc, 1);
+ JanetDictView dict = janet_getdictionary(argv, 0);
+ struct tm *tm = jd_tm_from_dict(dict);
+ return janet_wrap_abstract(tm);
+}
+
+JANET_FN(jd_gmtime,
+ "(gmtime (time))",
+ "") {
+ janet_fixarity(argc, 1);
+ time_t *time = jd_gettime(argv, 0);
+ struct tm *tm = gmtime(time);
+ struct tm *out = jd_maketm();
+ *out = *tm;
+ return janet_wrap_abstract(out);
+}
+
+JANET_FN(jd_localtime,
+ "(localtime (time))",
+ "WARNING: do not use this unless it's for final display.") {
+ janet_fixarity(argc, 1);
+ time_t *time = jd_gettime(argv, 0);
+ struct tm *tm = localtime(time);
+ struct tm *out = jd_maketm();
+ *out = *tm;
+ return janet_wrap_abstract(out);
+}
+
+JANET_FN(jd_time,
+ "(time)",
+ "") {
+ (void) argv;
+ janet_fixarity(argc, 0);
+ time_t *out = jd_maketime();
+ time(out);
+ return janet_wrap_abstract(out);
+}
+
+const JanetRegExt jd_time_cfuns[] = {
+ JANET_REG("dict->time", jd_dict_time),
+ JANET_REG("gmtime", jd_gmtime),
+ JANET_REG("localtime", jd_localtime),
+ JANET_REG("time", jd_time),
+ JANET_REG_END
+};
diff --git a/src/tm.c b/src/tm.c
new file mode 100644
index 0000000..5d2abff
--- /dev/null
+++ b/src/tm.c
@@ -0,0 +1,164 @@
+#include "date.h"
+#include "janet.h"
+
+// wrappers around struct tm
+
+static JanetMethod jd_tm_methods[] = {
+ {"mktime", jd_mktime},
+ {"mktime!", jd_mktime_inplace},
+ {"normalize", jd_mktime_inplace},
+ {"strftime", jd_strftime},
+ {"todict", jd_tm_dict},
+ {NULL, NULL},
+};
+
+static int jd_tm_compare(void *lhs, void *rhs) {
+ struct tm lhp = (*(struct tm*)lhs);
+ struct tm rhp = (*(struct tm*)rhs);
+ time_t lhv = mktime(&lhp);
+ time_t rhv = mktime(&rhp);
+ return difftime(lhv, rhv);
+}
+
+static int jd_tm_get(void *p, Janet key, Janet *out) {
+ if (!janet_checktype(key, JANET_KEYWORD)) {
+ return 0;
+ }
+
+ // is it a method?
+ if(janet_getmethod(janet_unwrap_keyword(key), jd_tm_methods, out)) {
+ return 1;
+ }
+
+ // piggyback off jd_tm_to_table
+ JanetTable *tb = jd_tm_to_table(p);
+ *out = janet_table_rawget(tb, key);
+
+ return janet_checktype(*out, JANET_NIL);
+}
+
+static const char* jd_tm_keys[] = {
+ "sec", "min", "hour", "mday", "mon", "year", "wday", "yday", NULL,
+};
+static Janet jd_tm_next(void *p, Janet key) {
+ (void) p;
+ const char **ptr = jd_tm_keys;
+ while (*ptr) {
+ if (janet_keyeq(key, *ptr)) {
+ return *(++ptr) ? janet_ckeywordv(*ptr) : janet_wrap_nil();
+ }
+ ptr++;
+ }
+ return janet_ckeywordv(jd_tm_keys[0]);
+}
+
+// struct tm can represent non-UTC
+// it does not keep TZ information so we can't display it without potentially lying
+static void jd_tm_tostring(void *p, JanetBuffer *buffer) {
+ strftime_buffer("%F %T.000", p, buffer);
+}
+
+static const JanetAbstractType jd_tm_t = {
+ "tm",
+ NULL,
+ NULL,
+ jd_tm_get,
+ NULL,
+ NULL,
+ NULL,
+ jd_tm_tostring,
+ jd_tm_compare,
+ NULL,
+ jd_tm_next,
+ JANET_ATEND_NEXT
+};
+
+struct tm *jd_gettm(Janet *argv, int32_t n) {
+ return (struct tm*)janet_getabstract(argv, n, &jd_tm_t);
+}
+
+struct tm *jd_maketm(void) {
+ return janet_abstract(&jd_tm_t, sizeof(struct tm));
+}
+
+JANET_FN(jd_dict_tm,
+ "",
+ "") {
+ janet_fixarity(argc, 1);
+ JanetDictView dict = janet_getdictionary(argv, 0);
+ return janet_wrap_abstract(jd_tm_from_dict(dict));
+}
+
+JANET_FN(jd_mktime,
+ "",
+ "") {
+ janet_fixarity(argc, 1);
+ struct tm *tm = jd_gettm(argv, 0);
+ struct tm *nw = jd_maketm();
+ *nw = *tm;
+ time_t *time = jd_maketime();
+ *time = mktime(nw);
+ return janet_wrap_abstract(time);
+}
+
+JANET_FN(jd_mktime_inplace,
+ "",
+ "") {
+ janet_fixarity(argc, 1);
+ struct tm *tm = jd_gettm(argv, 0);
+ time_t *time = jd_maketime();
+ *time = mktime(tm);
+ return janet_wrap_abstract(time);
+}
+
+JANET_FN(jd_tm_dict,
+ "",
+ "") {
+ janet_fixarity(argc, 1);
+ struct tm *tm = jd_gettm(argv, 0);
+ return janet_wrap_table(jd_tm_to_table(tm));
+}
+
+// strftime
+struct strftime_format {
+ const char *keyword;
+ const char *format;
+};
+const static struct strftime_format strftime_formats[] = {
+ {NULL, NULL},
+};
+JANET_FN(jd_strftime,
+ "",
+ "") {
+ janet_fixarity(argc, 2);
+ // tm is first for pseudo-OO
+ struct tm *tm = jd_gettm(argv, 0);
+
+ // determine format
+ const char *format = NULL;
+
+ // is it a preset?
+ if (janet_checktype(argv[1], JANET_KEYWORD)) {
+ const struct strftime_format *ptr = strftime_formats;
+ while (ptr->keyword) {
+ if (janet_keyeq(argv[1], ptr->keyword)) {
+ format = ptr->format;
+ break;
+ }
+ ptr++;
+ }
+ }
+
+ // preset not found
+ if (!format) format = janet_getcbytes(argv, 1);
+ return janet_wrap_buffer(strftime_buffer(format, tm, NULL));
+}
+
+const JanetRegExt jd_tm_cfuns[] = {
+ JANET_REG("dict->tm", jd_dict_tm),
+ JANET_REG("mktime", jd_mktime),
+ JANET_REG("mktime!", jd_mktime_inplace),
+ JANET_REG("strftime", jd_strftime),
+ JANET_REG("tm->dict", jd_tm_dict),
+ JANET_REG_END
+};
diff --git a/src/util.c b/src/util.c
new file mode 100644
index 0000000..3b6fd46
--- /dev/null
+++ b/src/util.c
@@ -0,0 +1,58 @@
+#include "date.h"
+
+#define JD_STRFTIME_CHUNK 64
+JanetBuffer *strftime_buffer(const char *format, const struct tm *tm, JanetBuffer *buffer) {
+ if (!buffer) buffer = janet_buffer(0);
+ size_t offset = buffer->count;
+ size_t written = 0;
+ do {
+ janet_buffer_extra(buffer, JD_STRFTIME_CHUNK);
+ written = strftime((char*)buffer->data + offset, buffer->capacity - offset, format, tm);
+ } while (!written);
+ buffer->count = written + offset; // does not include \0, but we don't want it anyway
+ return buffer;
+}
+
+static inline void tm_set_dict(JanetDictView dict, char *key, int *v) {
+ Janet k = janet_dictionary_get(dict.kvs, dict.cap, janet_ckeywordv(key));
+ *v = janet_checktype(k, JANET_NUMBER) ? janet_unwrap_integer(k) : 0;
+}
+struct tm *jd_tm_from_dict(JanetDictView dict) {
+ struct tm *tm = jd_maketm();
+
+ tm_set_dict(dict, "sec", &tm->tm_sec);
+ tm_set_dict(dict, "min", &tm->tm_min);
+ tm_set_dict(dict, "hour", &tm->tm_hour);
+ tm_set_dict(dict, "mday", &tm->tm_mday);
+ tm_set_dict(dict, "mon", &tm->tm_mon);
+ Janet year = janet_dictionary_get(dict.kvs, dict.cap, janet_ckeywordv("year"));
+ tm->tm_year = janet_checktype(year, JANET_NUMBER) ? janet_unwrap_integer(year) + 1900 : 1900;
+ tm_set_dict(dict, "wday", &tm->tm_wday);
+ tm_set_dict(dict, "yday", &tm->tm_yday);
+ Janet isdst = janet_dictionary_get(dict.kvs, dict.cap, janet_ckeywordv("isdst"));
+ tm->tm_isdst = janet_keyeq(isdst, "detect") ? -1 : (janet_truthy(isdst) ? 1 : 0);
+
+ return tm;
+}
+
+#define PUTV(T, K, V) janet_table_put(T, janet_ckeywordv(K), V)
+#define PUT(T, K, V) PUTV(T, K, janet_wrap_integer(V))
+JanetTable *jd_tm_to_table(struct tm *tm) {
+ JanetTable *out = janet_table(9);
+
+ PUT(out, "sec", tm->tm_sec);
+ PUT(out, "min", tm->tm_min);
+ PUT(out, "hour", tm->tm_hour);
+ PUT(out, "mday", tm->tm_mday);
+ PUT(out, "mon", tm->tm_mon);
+ PUT(out, "year", tm->tm_year + 1900);
+ PUT(out, "wday", tm->tm_wday);
+ PUT(out, "yday", tm->tm_yday);
+ if (tm->tm_isdst < 0) {
+ PUTV(out, "isdst", janet_ckeywordv("detect"));
+ } else {
+ PUTV(out, "isdst", tm->tm_isdst ? janet_wrap_true() : janet_wrap_false());
+ }
+
+ return out;
+}