diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/date.h | 28 | ||||
| -rw-r--r-- | src/main.c | 6 | ||||
| -rw-r--r-- | src/polyfill.c | 19 | ||||
| -rw-r--r-- | src/polyfill.h | 22 | ||||
| -rw-r--r-- | src/time.c | 100 | ||||
| -rw-r--r-- | src/tm.c | 164 | ||||
| -rw-r--r-- | src/util.c | 58 |
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; +} |
