diff options
-rw-r--r-- | builtin.c | 73 | ||||
-rw-r--r-- | configure.ac | 1 | ||||
-rw-r--r-- | docs/content/3.manual/manual.yml | 22 | ||||
-rw-r--r-- | jv.h | 6 | ||||
-rw-r--r-- | tests/all.test | 4 |
5 files changed, 104 insertions, 2 deletions
@@ -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: "`==`, `!=`" @@ -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 |