summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNicolas Williams <nico@cryptonector.com>2015-03-06 00:14:15 -0600
committerNicolas Williams <nico@cryptonector.com>2015-03-06 00:14:15 -0600
commita4b9552c82ef42a8bf7abf6f902d4f6e6c07a48f (patch)
treea0bf9b51d087b0808fc2ed780d1171d9ff6ddd10
parentb82c2319004fba89e06698a9df124b768e29a536 (diff)
Add date builtins (fix #364)
Windows support for strptime() is missing. We can add a copy of one from a BSD later.
-rw-r--r--builtin.c73
-rw-r--r--configure.ac1
-rw-r--r--docs/content/3.manual/manual.yml22
-rw-r--r--jv.h6
-rw-r--r--tests/all.test4
5 files changed, 104 insertions, 2 deletions
diff --git a/builtin.c b/builtin.c
index 0c1ee421..05d4c86c 100644
--- a/builtin.c
+++ b/builtin.c
@@ -1,4 +1,6 @@
+#define _XOPEN_SOURCE
#include <assert.h>
+#include <ctype.h>
#include <limits.h>
#include <math.h>
#ifdef HAVE_ONIGURUMA
@@ -6,6 +8,7 @@
#endif
#include <stdlib.h>
#include <string.h>
+#include <time.h>
#include "builtin.h"
#include "compile.h"
#include "jq_parser.h"
@@ -912,6 +915,74 @@ static jv f_stderr(jq_state *jq, jv input) {
return input;
}
+#ifdef HAVE_STRPTIME
+static jv f_strptime(jq_state *jq, jv a, jv b) {
+ if (jv_get_kind(a) != JV_KIND_STRING || jv_get_kind(b) != JV_KIND_STRING)
+ return jv_invalid_with_msg(jv_string("strptime/2 requires string inputs and arguments"));
+
+ struct tm tm;
+ const char *input = jv_string_value(a);
+ const char *fmt = jv_string_value(b);
+ const char *end = strptime(input, fmt, &tm);
+
+ if (end == NULL || (*end != '\0' && !isspace(*end))) {
+ jv e = jv_invalid_with_msg(jv_string_fmt("date \"%s\" does not match format \"%s\"", input, fmt));
+ jv_free(a);
+ jv_free(b);
+ return e;
+ }
+ jv_free(a);
+ jv_free(b);
+ jv r = JV_ARRAY(jv_number(tm.tm_year + 1900),
+ jv_number(tm.tm_mon),
+ jv_number(tm.tm_mday),
+ jv_number(tm.tm_hour),
+ jv_number(tm.tm_min),
+ jv_number(tm.tm_sec),
+ jv_number(tm.tm_wday),
+ jv_number(tm.tm_yday));
+ if (*end != '\0')
+ r = jv_array_append(r, jv_string(end));
+ return r;
+}
+#else
+static jv f_strptime(jq_state *jq, jv a, jv b) {
+ return jv_invalid_with_msg(jv_string("strptime/2 not implemented on this platform"));
+}
+#endif
+
+#define TO_TM_FIELD(t, j, i, k) \
+ do { \
+ jv n = jv_array_get(jv_copy(j), (i)); \
+ if (jv_get_kind(n) != (k)) \
+ return jv_invalid_with_msg(jv_string("mktime() requires a 'gmtime' input (an array inputs of 8 numeric values)")); \
+ t = jv_number_value(n); \
+ jv_free(n); \
+ } while (0)
+
+static jv f_mktime(jq_state *jq, jv a) {
+ if (jv_get_kind(a) != JV_KIND_ARRAY)
+ return jv_invalid_with_msg(jv_string("mktime() requires array inputs"));
+ if (jv_array_length(jv_copy(a)) < 6)
+ return jv_invalid_with_msg(jv_string("mktime() requires a 'gmtime' input (an array inputs of 8 numeric values)"));
+ struct tm tm;
+ memset(&tm, 0, sizeof(tm));
+ TO_TM_FIELD(tm.tm_year, a, 0, JV_KIND_NUMBER);
+ TO_TM_FIELD(tm.tm_mon, a, 1, JV_KIND_NUMBER);
+ TO_TM_FIELD(tm.tm_mday, a, 2, JV_KIND_NUMBER);
+ TO_TM_FIELD(tm.tm_hour, a, 3, JV_KIND_NUMBER);
+ TO_TM_FIELD(tm.tm_min, a, 4, JV_KIND_NUMBER);
+ TO_TM_FIELD(tm.tm_sec, a, 5, JV_KIND_NUMBER);
+ tm.tm_year -= 1900;
+ jv_free(a);
+ time_t t = mktime(&tm);
+ if (t == (time_t)-1)
+ return jv_invalid_with_msg(jv_string("invalid gmtime representation"));
+ return jv_number(t);
+}
+
+#undef TO_TM_FIELD
+
#define LIBM_DD(name) \
{(cfunction_ptr)f_ ## name, "_" #name, 1},
@@ -971,6 +1042,8 @@ static const struct cfunction function_list[] = {
{(cfunction_ptr)f_input, "_input", 1},
{(cfunction_ptr)f_debug, "debug", 1},
{(cfunction_ptr)f_stderr, "stderr", 1},
+ {(cfunction_ptr)f_strptime, "strptime", 2},
+ {(cfunction_ptr)f_mktime, "mktime", 1},
};
#undef LIBM_DD
diff --git a/configure.ac b/configure.ac
index 33ed602e..616f52a5 100644
--- a/configure.ac
+++ b/configure.ac
@@ -115,6 +115,7 @@ AM_CONDITIONAL([ENABLE_DOCS], [test "x$enable_docs" != xno])
AC_FIND_FUNC([isatty], [c], [#include <unistd.h>], [0])
AC_FIND_FUNC([_isatty], [c], [#include <io.h>], [0])
+AC_FIND_FUNC([strptime], [c], [#include <time.h>], [0])
AC_ARG_ENABLE([pthread-tls],
[AC_HELP_STRING([--enable-pthread-tls],
diff --git a/docs/content/3.manual/manual.yml b/docs/content/3.manual/manual.yml
index 02ca3caa..37f0eab4 100644
--- a/docs/content/3.manual/manual.yml
+++ b/docs/content/3.manual/manual.yml
@@ -1591,6 +1591,28 @@ sections:
input: "\"O'Hara's Ale\""
output: ["\"echo 'O'\\\\''Hara'\\\\''s Ale'\""]
+ - title: "Dates"
+ body: |
+
+ The `strptime(fmt)` function parses input strings matching the
+ `fmt` argument. The output is an array of eight numbers: the
+ year, the month, the day of the month, the hour of the day,
+ the minute of the hour, the second of the minute, the day of
+ the week, and the day of the year, all zero-based except for
+ the year and the month, which are one-based.
+
+ The `mktime` function consumes outputs from `strptime/1` and
+ produces the time in seconds since the Unix epoch.
+
+ examples:
+ - program: 'strptime'
+ input: '"2015-03-05 23:51:47Z"'
+ output: ['[2015,2,5,23,51,47,4,63]']
+
+ - program: 'strptime|mktime'
+ input: '"2015-03-05 23:51:47Z"'
+ output: ['1425621107']
+
- title: Conditionals and Comparisons
entries:
- title: "`==`, `!=`"
diff --git a/jv.h b/jv.h
index 8ed35c14..5f2b318c 100644
--- a/jv.h
+++ b/jv.h
@@ -87,9 +87,11 @@ jv jv_array_indexes(jv, jv);
#define JV_ARRAY_5(e1,e2,e3,e4,e5) (jv_array_append(JV_ARRAY_4(e1,e2,e3,e4),e5))
#define JV_ARRAY_6(e1,e2,e3,e4,e5,e6) (jv_array_append(JV_ARRAY_5(e1,e2,e3,e4,e5),e6))
#define JV_ARRAY_7(e1,e2,e3,e4,e5,e6,e7) (jv_array_append(JV_ARRAY_6(e1,e2,e3,e4,e5,e6),e7))
-#define JV_ARRAY_IDX(_1,_2,_3,_4,_5,_6,_7,NAME,...) NAME
+#define JV_ARRAY_8(e1,e2,e3,e4,e5,e6,e7,e8) (jv_array_append(JV_ARRAY_7(e1,e2,e3,e4,e5,e6,e7),e8))
+#define JV_ARRAY_9(e1,e2,e3,e4,e5,e6,e7,e8,e9) (jv_array_append(JV_ARRAY_8(e1,e2,e3,e4,e5,e6,e7,e8),e9))
+#define JV_ARRAY_IDX(_1,_2,_3,_4,_5,_6,_7,_8,_9,NAME,...) NAME
#define JV_ARRAY(...) \
- JV_ARRAY_IDX(__VA_ARGS__, JV_ARRAY_7, JV_ARRAY_6, JV_ARRAY_5, JV_ARRAY_4, JV_ARRAY_3, JV_ARRAY_2, JV_ARRAY_1)(__VA_ARGS__)
+ JV_ARRAY_IDX(__VA_ARGS__, JV_ARRAY_9, JV_ARRAY_8, JV_ARRAY_7, JV_ARRAY_6, JV_ARRAY_5, JV_ARRAY_4, JV_ARRAY_3, JV_ARRAY_2, JV_ARRAY_1)(__VA_ARGS__)
#ifdef __GNUC__
#define JV_PRINTF_LIKE(fmt_arg_num, args_num) \
diff --git a/tests/all.test b/tests/all.test
index 0d3526e9..3b22f4de 100644
--- a/tests/all.test
+++ b/tests/all.test
@@ -1140,6 +1140,10 @@ bsearch(4)
[1,2,3]
-4
+[strptime("%Y-%m-%d %H:%M:%SZ")|(.,mktime)]
+"2015-03-05 23:51:47Z"
+[[2015,2,5,23,51,47,4,63],1425621107]
+
# module system
import "a" as foo; import "b" as bar; def fooa: foo::a; [fooa, bar::a, bar::b, foo::a]
null