diff --git a/configure.ac b/configure.ac index 4a72465979..ad70ed0e04 100644 --- a/configure.ac +++ b/configure.ac @@ -140,14 +140,21 @@ AC_FIND_FUNC([strptime], [c], [#include ], [0, 0, 0]) AC_FIND_FUNC([strftime], [c], [#include ], [0, 0, 0, 0]) AC_FIND_FUNC([setenv], [c], [#include ], [0, 0, 0]) AC_FIND_FUNC([timegm], [c], [#include ], [0]) +AC_FIND_FUNC([timelocal], [c], [#include ], [0]) AC_FIND_FUNC([gmtime_r], [c], [#include ], [0, 0]) AC_FIND_FUNC([gmtime], [c], [#include ], [0]) AC_FIND_FUNC([localtime_r], [c], [#include ], [0, 0]) AC_FIND_FUNC([localtime], [c], [#include ], [0]) +AC_FIND_FUNC([mktime], [c], [#include ], [0]) +AC_FIND_FUNC([_mkgmtime], [c], [#include ], [0]) +AC_FIND_FUNC([difftime], [c], [#include ], [0, 0]) AC_FIND_FUNC([gettimeofday], [c], [#include ], [0, 0]) -AC_CHECK_MEMBER([struct tm.tm_gmtoff], [AC_DEFINE([HAVE_TM_TM_GMT_OFF],1,[Define to 1 if the system has the tm_gmt_off field in struct tm])], +AC_STRUCT_TIMEZONE +AC_CHECK_MEMBER([struct tm.tm_gmtoff], [AC_DEFINE([HAVE_TM_TM_GMTOFF],1,[Define to 1 if the system has the tm_gmtoff field in struct tm])], [], [[#include ]]) -AC_CHECK_MEMBER([struct tm.__tm_gmtoff], [AC_DEFINE([HAVE_TM___TM_GMT_OFF],1,[Define to 1 if the system has the __tm_gmt_off field in struct tm])], +AC_CHECK_MEMBER([struct tm.__tm_gmtoff], [AC_DEFINE([HAVE_TM___TM_GMTOFF],1,[Define to 1 if the system has the __tm_gmtoff field in struct tm])], + [], [[#include ]]) +AC_CHECK_MEMBER([struct tm.tm_zone], [AC_DEFINE([HAVE_TM_TM_ZONE],1,[Define to 1 if the system has the tm_zone field in struct tm])], [], [[#include ]]) AC_FIND_FUNC([setlocale], [c], [#include ], [0,0]) diff --git a/docs/content/manual/manual.yml b/docs/content/manual/manual.yml index f28d9a89f6..b47a1174b9 100644 --- a/docs/content/manual/manual.yml +++ b/docs/content/manual/manual.yml @@ -2187,10 +2187,11 @@ sections: Low-level jq interfaces to the C-library time functions are also provided: `strptime`, `strftime`, `strflocaltime`, - `mktime`, `gmtime`, and `localtime`. Refer to your host - operating system's documentation for the format strings used - by `strptime` and `strftime`. Note: these are not necessarily - stable interfaces in jq, particularly as to their localization + `mktime`, `gmtime`, `localtime`, `timegm`, and + `timelocal`. Refer to your host operating system's + documentation for the format strings used by `strptime` + and `strftime`. Note: these are not necessarily stable + interfaces in jq, particularly as to their localization functionality. The `gmtime` builtin consumes a number of seconds since the @@ -2199,15 +2200,27 @@ sections: (in this order): the year, the month (zero-based), the day of the month (one-based), 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 one-based unless otherwise stated. The - day of the week number may be wrong on some systems for dates - before March 1st 1900, or after December 31 2099. + day of the year -all one-based unless otherwise stated- + followed by (depending on platform support) a boolean + indicating whether the time is in a daylight savings + timezone, followed by the offset to UTC, followed by the + name of the timezone. The day of the week number may be + wrong on some systems for dates before March 1st 1900, or + after December 31 2099. The `localtime` builtin works like the `gmtime` builtin, but using the local timezone setting. - The `mktime` builtin consumes "broken down time" - representations of time output by `gmtime` and `strptime`. + The `mktime`, `timegm` and `timelocal` builtins do the + opposite of `gmtime` and `localtime`, respectively: they + consume "broken down time" representations of time + output by `gmtime`, `localtime`, and `strptime`, and + output a number of seconds since the Unix epoch. + + Note that the C `mktime()` function interprets its broken + down time input as local time, while the jq `mktime` + function interpretes its broken down time as UTC. It is + better to use `timelocal` and `timegm` to avoid confusion. The `strptime(fmt)` builtin parses input strings matching the `fmt` argument. The output is in the "broken down time" @@ -2230,11 +2243,11 @@ sections: input: '"2015-03-05T23:51:47Z"' output: ['1425599507'] - - program: 'strptime("%Y-%m-%dT%H:%M:%SZ")' + - program: 'strptime("%Y-%m-%dT%H:%M:%SZ")[0:8]' input: '"2015-03-05T23:51:47Z"' output: ['[2015,2,5,23,51,47,4,63]'] - - program: 'strptime("%Y-%m-%dT%H:%M:%SZ")|mktime' + - program: 'strptime("%Y-%m-%dT%H:%M:%SZ")|timegm' input: '"2015-03-05T23:51:47Z"' output: ['1425599507'] diff --git a/jq.1.prebuilt b/jq.1.prebuilt index 3c65674849..e512091619 100644 --- a/jq.1.prebuilt +++ b/jq.1.prebuilt @@ -2385,16 +2385,19 @@ The \fBtodate\fR builtin is an alias for \fBtodateiso8601\fR\. The \fBnow\fR builtin outputs the current time, in seconds since the Unix epoch\. . .P -Low\-level jq interfaces to the C\-library time functions are also provided: \fBstrptime\fR, \fBstrftime\fR, \fBstrflocaltime\fR, \fBmktime\fR, \fBgmtime\fR, and \fBlocaltime\fR\. Refer to your host operating system\'s documentation for the format strings used by \fBstrptime\fR and \fBstrftime\fR\. Note: these are not necessarily stable interfaces in jq, particularly as to their localization functionality\. +Low\-level jq interfaces to the C\-library time functions are also provided: \fBstrptime\fR, \fBstrftime\fR, \fBstrflocaltime\fR, \fBmktime\fR, \fBgmtime\fR, \fBlocaltime\fR, \fBtimegm\fR, and \fBtimelocal\fR\. Refer to your host operating system\'s documentation for the format strings used by \fBstrptime\fR and \fBstrftime\fR\. Note: these are not necessarily stable interfaces in jq, particularly as to their localization functionality\. . .P -The \fBgmtime\fR builtin consumes a number of seconds since the Unix epoch and outputs a "broken down time" representation of Greenwich Mean Time as an array of numbers representing (in this order): the year, the month (zero\-based), the day of the month (one\-based), 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 one\-based unless otherwise stated\. The day of the week number may be wrong on some systems for dates before March 1st 1900, or after December 31 2099\. +The \fBgmtime\fR builtin consumes a number of seconds since the Unix epoch and outputs a "broken down time" representation of Greenwich Mean Time as an array of numbers representing (in this order): the year, the month (zero\-based), the day of the month (one\-based), 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 one\-based unless otherwise stated\- followed by (depending on platform support) a boolean indicating whether the time is in a daylight savings timezone, followed by the offset to UTC, followed by the name of the timezone\. The day of the week number may be wrong on some systems for dates before March 1st 1900, or after December 31 2099\. . .P The \fBlocaltime\fR builtin works like the \fBgmtime\fR builtin, but using the local timezone setting\. . .P -The \fBmktime\fR builtin consumes "broken down time" representations of time output by \fBgmtime\fR and \fBstrptime\fR\. +The \fBmktime\fR, \fBtimegm\fR and \fBtimelocal\fR builtins do the opposite of \fBgmtime\fR and \fBlocaltime\fR, respectively: they consume "broken down time" representations of time output by \fBgmtime\fR, \fBlocaltime\fR, and \fBstrptime\fR, and output a number of seconds since the Unix epoch\. +. +.P +Note that the C \fBmktime()\fR function interprets its broken down time input as local time, while the jq \fBmktime\fR function interpretes its broken down time as UTC\. It is better to use \fBtimelocal\fR and \fBtimegm\fR to avoid confusion\. . .P The \fBstrptime(fmt)\fR builtin parses input strings matching the \fBfmt\fR argument\. The output is in the "broken down time" representation consumed by \fBgmtime\fR and output by \fBmktime\fR\. @@ -2416,11 +2419,11 @@ jq \'fromdate\' "2015\-03\-05T23:51:47Z" => 1425599507 -jq \'strptime("%Y\-%m\-%dT%H:%M:%SZ")\' +jq \'strptime("%Y\-%m\-%dT%H:%M:%SZ")[0:8]\' "2015\-03\-05T23:51:47Z" => [2015,2,5,23,51,47,4,63] -jq \'strptime("%Y\-%m\-%dT%H:%M:%SZ")|mktime\' +jq \'strptime("%Y\-%m\-%dT%H:%M:%SZ")|timegm\' "2015\-03\-05T23:51:47Z" => 1425599507 . diff --git a/src/builtin.c b/src/builtin.c index 2ea40dc54f..9c9f484afa 100644 --- a/src/builtin.c +++ b/src/builtin.c @@ -1287,14 +1287,28 @@ static jv f_stderr(jq_state *jq, jv input) { } static jv tm2jv(struct tm *tm) { - return JV_ARRAY(jv_number(tm->tm_year + 1900), + jv a = 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)); + jv_number(tm->tm_yday), + tm->tm_isdst ? jv_true() : jv_false()); + // JV_ARRAY() only handles up to 9 arguments +#ifdef HAVE_TM_TM_GMTOFF + a = jv_array_append(a, jv_number(tm->tm_gmtoff)); +#else + a = jv_array_append(a, jv_null()); +#endif +#ifdef HAVE_STRUCT_TM_TM_ZONE + if (tm->tm_zone) + return jv_array_append(a, jv_string(tm->tm_zone)); +#else + a = jv_array_append(a, jv_null()); +#endif + return a; } #if defined(WIN32) && !defined(HAVE_SETENV) @@ -1318,50 +1332,6 @@ static int setenv(const char *var, const char *val, int ovr) } #endif -/* - * mktime() has side-effects and anyways, returns time in the local - * timezone, not UTC. We want timegm(), which isn't standard. - * - * To make things worse, mktime() tells you what the timezone - * adjustment is, but you have to #define _BSD_SOURCE to get this - * field of struct tm on some systems. - * - * This is all to blame on POSIX, of course. - * - * Our wrapper tries to use timegm() if available, or mktime() and - * correct for its side-effects if possible. - * - * Returns (time_t)-2 if mktime()'s side-effects cannot be corrected. - */ -static time_t my_mktime(struct tm *tm) { -#ifdef HAVE_TIMEGM - return timegm(tm); -#elif HAVE_TM_TM_GMT_OFF - - time_t t = mktime(tm); - if (t == (time_t)-1) - return t; - return t + tm->tm_gmtoff; -#elif HAVE_TM___TM_GMT_OFF - time_t t = mktime(tm); - if (t == (time_t)-1) - return t; - return t + tm->__tm_gmtoff; -#elif WIN32 - return _mkgmtime(tm); -#else - char *tz; - - tz = (tz = getenv("TZ")) != NULL ? strdup(tz) : NULL; - if (tz != NULL) - setenv("TZ", "", 1); - time_t t = mktime(tm); - if (tz != NULL) - setenv("TZ", tz, 1); - return t; -#endif -} - /* Compute and set tm_wday */ static void set_tm_wday(struct tm *tm) { /* @@ -1476,29 +1446,108 @@ static jv f_strptime(jq_state *jq, jv a, jv b) { return r; } -#define TO_TM_FIELD(t, j, i) \ - do { \ - jv n = jv_array_get(jv_copy(j), (i)); \ - if (jv_get_kind(n) != (JV_KIND_NUMBER)) { \ - jv_free(n); \ - jv_free(j); \ - return 0; \ - } \ - t = jv_number_value(n); \ - jv_free(n); \ - } while (0) +/* + * Utility function for jv2tm() that returns `ifnan` if `v` is not a number in + * any way, and clamps the value range of `v` to `imin` and `imax`. + */ +static int toint(jv v, int imin, int imax, int ifnan) { + if (jv_get_kind(v) != (JV_KIND_NUMBER) || jvp_number_is_nan(v)) { + jv_free(v); + return ifnan; + } + double n = jv_number_value(v); + jv_free(v); + if (isinf(n)) { + if (n < 0.0) + return imin; + return imax; + } + if (n < imin) + return imin; + if (n > imax) + return imax; + return (int)n; +} -static int jv2tm(jv a, struct tm *tm) { +static int jv2tm(jv a, struct tm *tm, double *frac, char **freeme) { + int ret = 1; + + *freeme = NULL; memset(tm, 0, sizeof(*tm)); - TO_TM_FIELD(tm->tm_year, a, 0); + if ( (tm->tm_year = toint(jv_array_get(jv_copy(a), 0), + 0, 10000, 0)) < 1 + || tm->tm_year > 9999 + || (tm->tm_mon = toint(jv_array_get(jv_copy(a), 1), -1, 12, -1)) < 0 + || tm->tm_mon > 11 + || (tm->tm_mday = toint(jv_array_get(jv_copy(a), 2), 0, 32, 0)) < 1 + || tm->tm_mday > 31 + || (tm->tm_hour = toint(jv_array_get(jv_copy(a), 3), -1, 24, -1)) < 0 + || tm->tm_hour > 23 + || (tm->tm_min = toint(jv_array_get(jv_copy(a), 4), -1, 60, -1)) < 0 + || tm->tm_min > 59 + || (tm->tm_sec = toint(jv_array_get(jv_copy(a), 5), -1, 61, -1)) < 0 + || tm->tm_sec > 60 + || (tm->tm_wday = toint(jv_array_get(jv_copy(a), 6), -1, 7, -1)) < 0 + || tm->tm_wday > 6 + || (tm->tm_yday = toint(jv_array_get(jv_copy(a), 7), -1, 366, -1)) < 0 + || tm->tm_yday > 365) { + jv_free(a); + return 0; + } tm->tm_year -= 1900; - TO_TM_FIELD(tm->tm_mon, a, 1); - TO_TM_FIELD(tm->tm_mday, a, 2); - TO_TM_FIELD(tm->tm_hour, a, 3); - TO_TM_FIELD(tm->tm_min, a, 4); - TO_TM_FIELD(tm->tm_sec, a, 5); - TO_TM_FIELD(tm->tm_wday, a, 6); - TO_TM_FIELD(tm->tm_yday, a, 7); + if (frac) { + // We already know this is a number, not a NaN, and in range. We would + // have returned already otherwise. + jv n = jv_array_get(jv_copy(a), 5); + double d = jv_number_value(n); + jv_free(n); + *frac = d - tm->tm_sec; + } + jv v = jv_array_get(jv_copy(a), 8); + switch (jv_get_kind(v)) { + case JV_KIND_INVALID: break; // Optional field not present + case JV_KIND_FALSE: break; + case JV_KIND_TRUE: tm->tm_isdst = 1; break; + case JV_KIND_NUMBER: tm->tm_isdst = toint(v, 0, 1, 0); break; + default: ret = 0; break; + } + jv_free(v); + v = jv_array_get(jv_copy(a), 9); + int gmtoff; + switch (jv_get_kind(v)) { + case JV_KIND_NULL: break; // See tm2jv() + case JV_KIND_INVALID: break; // Optional field not present + default: + gmtoff = toint(jv_array_get(jv_copy(a), 9), -12 * 3600 - 1, 12 * 3600 + 1, 0); + if (gmtoff < -12 * 3600 || gmtoff > 12 * 3600) + ret = 0; +#ifdef HAVE_TM_TM_GMTOFF + tm->tm_gmtoff = gmtoff; +#endif + break; + } + jv_free(v); + v = jv_array_get(jv_copy(a), 10); + switch (jv_get_kind(v)) { + case JV_KIND_NULL: break; // See tm2jv() + case JV_KIND_INVALID: break; // Optional field not present + case JV_KIND_STRING: + if (jv_get_kind(v) == JV_KIND_STRING) { + if ((*freeme = strdup(jv_string_value(v))) == NULL) + ret = 0; + } else if (jv_is_valid(v)) { + ret = 0; + } + break; + default: + ret = 0; + break; + } + jv_free(v); +#ifdef HAVE_STRUCT_TM_TM_ZONE + tm->tm_zone = *freeme; +#endif + jv_free(a); // We use UTC everywhere (gettimeofday, gmtime) and UTC does not do DST. @@ -1509,25 +1558,87 @@ static int jv2tm(jv a, struct tm *tm) { // hope it is okay to initialize them to zero, because the standard does not // provide an alternative. - return 1; + if (!ret) { + free(*freeme); + *freeme = NULL; + } + return ret; } -#undef TO_TM_FIELD - static jv f_mktime(jq_state *jq, jv a) { if (jv_get_kind(a) != JV_KIND_ARRAY) return ret_error(a, jv_string("mktime requires array inputs")); - if (jv_array_length(jv_copy(a)) < 6) - return ret_error(a, jv_string("mktime requires parsed datetime inputs")); + double frac; struct tm tm; - if (!jv2tm(a, &tm)) + char *freeme; + if (!jv2tm(a, &tm, &frac, &freeme)) return jv_invalid_with_msg(jv_string("mktime requires parsed datetime inputs")); - time_t t = my_mktime(&tm); + /* + * mktime() has side-effects and anyways, returns time in the local + * timezone, not UTC. Does timelocal() have side-effects? + * + * We try to use timegm() if available, else mktime(). + */ +#if HAVE_TIMEGM + time_t t = timegm(&tm); +#elif defined(WIN32) + time_t t = _mkgmtime(&tm); +#else + time_t t = mktime(&tm); +#ifdef HAVE_TM_TM_GMTOFF + t += tm.tm_gmtoff; +#else + /* Bah, what can we do? */ +#endif +#endif + free(freeme); if (t == (time_t)-1) return jv_invalid_with_msg(jv_string("invalid gmtime representation")); if (t == (time_t)-2) return jv_invalid_with_msg(jv_string("mktime not supported on this platform")); - return jv_number(t); + return jv_number(t + frac); +} + +static jv f_timelocal(jq_state *jq, jv a) { + if (jv_get_kind(a) != JV_KIND_ARRAY) + return ret_error(a, jv_string("timelocal requires array inputs")); + double frac; + struct tm tm; + char *freeme; + if (!jv2tm(a, &tm, &frac, &freeme)) + return jv_invalid_with_msg(jv_string("timelocal requires parsed datetime inputs")); +#ifdef HAVE_TIMELOCAL + time_t t = timelocal(&tm); +#else + time_t t = mktime(&tm); +#endif + free(freeme); + if (t == (time_t)-1) + return jv_invalid_with_msg(jv_string("invalid gmtime representation")); + return jv_number(t + frac); +} + +static jv f_timegm(jq_state *jq, jv a) { + if (jv_get_kind(a) != JV_KIND_ARRAY) + return ret_error(a, jv_string("timegm requires array inputs")); + double frac; + struct tm tm; + char *freeme; + if (!jv2tm(a, &tm, &frac, &freeme)) + return jv_invalid_with_msg(jv_string("timegm requires parsed datetime inputs")); +#ifdef HAVE_TIMEGM + time_t t = timegm(&tm); + free(freeme); +#elif defined(HAVE__MKGMTIME) + time_t t = _mkgmtime(&tm); // Windows has _mkgmtime() + free(freeme); +#else + free(freeme); + return jv_invalid_with_msg(jv_string("timegm is not supported")); +#endif + if (t == (time_t)-1) + return jv_invalid_with_msg(jv_string("invalid gmtime representation")); + return jv_number(t + frac); } #ifdef HAVE_GMTIME_R @@ -1618,7 +1729,8 @@ static jv f_strftime(jq_state *jq, jv a, jv b) { return ret_error2(a, b, jv_string("strftime/1 requires a string format")); } struct tm tm; - if (!jv2tm(a, &tm)) + char *freeme; + if (!jv2tm(a, &tm, NULL, &freeme)) return ret_error(b, jv_string("strftime/1 requires parsed datetime inputs")); const char *fmt = jv_string_value(b); @@ -1626,6 +1738,7 @@ static jv f_strftime(jq_state *jq, jv a, jv b) { char *buf = alloca(alloced); size_t n = strftime(buf, alloced, fmt, &tm); jv_free(b); + free(freeme); /* POSIX doesn't provide errno values for strftime() failures; weird */ if (n == 0 || n > alloced) return jv_invalid_with_msg(jv_string("strftime/1: unknown system failure")); @@ -1643,19 +1756,25 @@ static jv f_strftime(jq_state *jq, jv a, jv b) { static jv f_strflocaltime(jq_state *jq, jv a, jv b) { if (jv_get_kind(a) == JV_KIND_NUMBER) { a = f_localtime(jq, a); + if (!jv_is_valid(a)) { + jv_free(b); + return a; + } } else if (jv_get_kind(a) != JV_KIND_ARRAY) { return ret_error2(a, b, jv_string("strflocaltime/1 requires parsed datetime inputs")); } else if (jv_get_kind(b) != JV_KIND_STRING) { return ret_error2(a, b, jv_string("strflocaltime/1 requires a string format")); } struct tm tm; - if (!jv2tm(a, &tm)) + char *freeme; + if (!jv2tm(a, &tm, NULL, &freeme)) return ret_error(b, jv_string("strflocaltime/1 requires parsed datetime inputs")); const char *fmt = jv_string_value(b); size_t alloced = strlen(fmt) + 100; char *buf = alloca(alloced); size_t n = strftime(buf, alloced, fmt, &tm); jv_free(b); + free(freeme); /* POSIX doesn't provide errno values for strftime() failures; weird */ if (n == 0 || n > alloced) return jv_invalid_with_msg(jv_string("strflocaltime/1: unknown system failure")); @@ -1778,6 +1897,8 @@ BINOPS {f_mktime, "mktime", 1}, {f_gmtime, "gmtime", 1}, {f_localtime, "localtime", 1}, + {f_timegm, "timegm", 1}, + {f_timelocal, "timelocal", 1}, {f_now, "now", 1}, {f_current_filename, "input_filename", 1}, {f_current_line, "input_line_number", 1}, diff --git a/src/builtin.jq b/src/builtin.jq index 1e23a2ed66..67d9b1887f 100644 --- a/src/builtin.jq +++ b/src/builtin.jq @@ -70,7 +70,7 @@ def _flatten($x): reduce .[] as $i ([]; if $i | type == "array" and $x != 0 then def flatten($x): if $x < 0 then error("flatten depth must not be negative") else _flatten($x) end; def flatten: _flatten(-1); def range($x): range(0;$x); -def fromdateiso8601: strptime("%Y-%m-%dT%H:%M:%SZ")|mktime; +def fromdateiso8601: strptime("%Y-%m-%dT%H:%M:%SZ")|timegm; def todateiso8601: strftime("%Y-%m-%dT%H:%M:%SZ"); def fromdate: fromdateiso8601; def todate: todateiso8601; diff --git a/src/util.c b/src/util.c index e35eb9067d..e2edd97622 100644 --- a/src/util.c +++ b/src/util.c @@ -550,7 +550,7 @@ static const unsigned char *find_string(const unsigned char *, int *, const char #define strncasecmp _strnicmp #endif -#ifdef TM_ZONE +#ifdef HAVE_STRUCT_TM_TM_ZONE static char* utc = "UTC"; #endif /* RFC-822/RFC-2822 */ @@ -630,12 +630,12 @@ fromzone(const unsigned char **bp, struct tm *tm, int mandatory) *bp = rp; tm->tm_isdst = 0; /* XXX */ -#ifdef TM_GMTOFF - tm->TM_GMTOFF = tzgetgmtoff(tz, tm->tm_isdst); +#ifdef HAVE_TM_TM_GMTOFF + tm->tm_gmtoff = tzgetgmtoff(tz, tm->tm_isdst); #endif -#ifdef TM_ZONE +#ifdef HAVE_STRUCT_TM_TM_ZONE // Can't use tzgetname() here because we are going to free() - tm->TM_ZONE = NULL; /* XXX */ + tm->tm_zone = NULL; /* XXX */ #endif // tzfree(tz); return 1; @@ -993,11 +993,11 @@ again: switch (c = *fmt++) { if (!delim(*bp)) goto namedzone; tm->tm_isdst = 0; -#ifdef TM_GMTOFF - tm->TM_GMTOFF = 0; +#ifdef HAVE_TM_TM_GMTOFF + tm->tm_gmtoff = 0; #endif -#ifdef TM_ZONE - tm->TM_ZONE = utc; +#ifdef HAVE_STRUCT_TM_TM_ZONE + tm->tm_zone = utc; #endif continue; case '+': @@ -1014,30 +1014,30 @@ again: switch (c = *fmt++) { if (delim(bp[1]) && ((*bp >= 'A' && *bp <= 'I') || (*bp >= 'L' && *bp <= 'Y'))) { -#ifdef TM_GMTOFF +#ifdef HAVE_TM_TM_GMTOFF /* Argh! No 'J'! */ if (*bp >= 'A' && *bp <= 'I') - tm->TM_GMTOFF = + tm->tm_gmtoff = (int)*bp - ('A' - 1); else if (*bp >= 'L' && *bp <= 'M') - tm->TM_GMTOFF = (int)*bp - 'A'; + tm->tm_gmtoff = (int)*bp - 'A'; else if (*bp >= 'N' && *bp <= 'Y') - tm->TM_GMTOFF = 'M' - (int)*bp; - tm->TM_GMTOFF *= SECSPERHOUR; + tm->tm_gmtoff = 'M' - (int)*bp; + tm->tm_gmtoff *= SECSPERHOUR; #endif -#ifdef TM_ZONE - tm->TM_ZONE = NULL; /* XXX */ +#ifdef HAVE_STRUCT_TM_TM_ZONE + tm->tm_zone = NULL; /* XXX */ #endif bp++; continue; } /* 'J' is local time */ if (delim(bp[1]) && *bp == 'J') { -#ifdef TM_GMTOFF - tm->TM_GMTOFF = -timezone; +#ifdef HAVE_TM_TM_GMTOFF + tm->tm_gmtoff = -timezone; #endif -#ifdef TM_ZONE - tm->TM_ZONE = NULL; /* XXX */ +#ifdef HAVE_STRUCT_TM_TM_ZONE + tm->tm_zone = NULL; /* XXX */ #endif bp++; continue; @@ -1052,11 +1052,11 @@ again: switch (c = *fmt++) { goto loadzone; ep = find_string(bp, &i, nast, NULL, 4); if (ep != NULL) { -#ifdef TM_GMTOFF - tm->TM_GMTOFF = (-5 - i) * SECSPERHOUR; +#ifdef HAVE_TM_TM_GMTOFF + tm->tm_gmtoff = (-5 - i) * SECSPERHOUR; #endif -#ifdef TM_ZONE - tm->TM_ZONE = __UNCONST(nast[i]); +#ifdef HAVE_STRUCT_TM_TM_ZONE + tm->tm_zone = __UNCONST(nast[i]); #endif bp = ep; continue; @@ -1064,11 +1064,11 @@ again: switch (c = *fmt++) { ep = find_string(bp, &i, nadt, NULL, 4); if (ep != NULL) { tm->tm_isdst = 1; -#ifdef TM_GMTOFF - tm->TM_GMTOFF = (-4 - i) * SECSPERHOUR; +#ifdef HAVE_TM_TM_GMTOFF + tm->tm_gmtoff = (-4 - i) * SECSPERHOUR; #endif -#ifdef TM_ZONE - tm->TM_ZONE = __UNCONST(nadt[i]); +#ifdef HAVE_STRUCT_TM_TM_ZONE + tm->tm_zone = __UNCONST(nadt[i]); #endif bp = ep; continue; @@ -1081,11 +1081,11 @@ again: switch (c = *fmt++) { NULL, 2); if (ep != NULL) { tm->tm_isdst = i; -#ifdef TM_GMTOFF - tm->TM_GMTOFF = -timezone; +#ifdef HAVE_TM_TM_GMTOFF + tm->tm_gmtoff = -timezone; #endif -#ifdef TM_ZONE - tm->TM_ZONE = tzname[i]; +#ifdef HAVE_STRUCT_TM_TM_ZONE + tm->tm_zone = tzname[i]; #endif bp = ep; continue; @@ -1138,11 +1138,11 @@ again: switch (c = *fmt++) { if (neg) offs = -offs; tm->tm_isdst = 0; /* XXX */ -#ifdef TM_GMTOFF - tm->TM_GMTOFF = offs; +#ifdef HAVE_TM_TM_GMTOFF + tm->tm_gmtoff = offs; #endif -#ifdef TM_ZONE - tm->TM_ZONE = NULL; /* XXX */ +#ifdef HAVE_STRUCT_TM_TM_ZONE + tm->tm_zone = NULL; /* XXX */ #endif continue; diff --git a/tests/jq.test b/tests/jq.test index 9d0b59285b..c584215126 100644 --- a/tests/jq.test +++ b/tests/jq.test @@ -1563,9 +1563,9 @@ strftime("%A, %B %d, %Y") 1435677542.822351 "Tuesday, June 30, 2015" -gmtime +gmtime|(.[9] |= if .==null then 0 else . end)|(.[10] |= if (.=="UTC") or (.==null) then "GMT" else . end) 1425599507 -[2015,2,5,23,51,47,4,63] +[2015,2,5,23,51,47,4,63,false,0,"GMT"] # test invalid tm input try strftime("%Y-%m-%dT%H:%M:%SZ") catch . @@ -1580,6 +1580,15 @@ try mktime catch . ["a",1,2,3,4,5,6,7] "mktime requires parsed datetime inputs" +# #2863 +[.[]|try (timegm | error) catch .]|[length,unique[]] +[[0000],[10000],[1970,-1],[1970,12],[1970,11,0],[1970,11,32],[1970,11,30,-1],[1970,11,30,24],[1970,11,30,23,-1],[1970,11,31,23,60],[1970,11,30,23,59,-1],[1970,11,30,23,59,61],[1970,11,30,23,59,60,-1],[1970,11,30,23,59,60,7],[1970,11,30,23,59,60,4],[1970,11,30,23,59,60,4,-1],[1970,11,30,23,59,60,4,366],[1970,11,30,23,59,60,4,364,true,-43201],[1970,11,30,23,59,60,4,364,true,43201],[1970,11,30,23,59,60,4,364,true,0,true]] +[20,"timegm requires parsed datetime inputs"] + +[.[]|timegm]|length +[[1970,11,30,23,59,60,4,364],[1970,11,30,23,59,60,4,364,true,-43200],[1970,11,30,23,59,60,4,364,true,43200],[1970,11,30,23,59,60,4,364,true,43200,"EDT"]] +4 + # module system import "a" as foo; import "b" as bar; def fooa: foo::a; [fooa, bar::a, bar::b, foo::a] null diff --git a/tests/man.test b/tests/man.test index 822de5ab9a..a64efa903f 100644 --- a/tests/man.test +++ b/tests/man.test @@ -718,11 +718,11 @@ fromdate "2015-03-05T23:51:47Z" 1425599507 -strptime("%Y-%m-%dT%H:%M:%SZ") +strptime("%Y-%m-%dT%H:%M:%SZ")[0:8] "2015-03-05T23:51:47Z" [2015,2,5,23,51,47,4,63] -strptime("%Y-%m-%dT%H:%M:%SZ")|mktime +strptime("%Y-%m-%dT%H:%M:%SZ")|timegm "2015-03-05T23:51:47Z" 1425599507 diff --git a/tests/optional.test b/tests/optional.test index 85bc9e9941..e009f68227 100644 --- a/tests/optional.test +++ b/tests/optional.test @@ -1,20 +1,23 @@ # See tests/jq.test and the jq manual for more information. # strptime() is not available on mingw/WIN32 -[strptime("%Y-%m-%dT%H:%M:%SZ")|(.,mktime)] +[strptime("%Y-%m-%dT%H:%M:%SZ")|(.,mktime,timegm)] "2015-03-05T23:51:47Z" -[[2015,2,5,23,51,47,4,63],1425599507] +[[2015,2,5,23,51,47,4,63,false,0],1425599507,1425599507] # Check day-of-week and day of year computations # (should trip an assert if this fails) # This date range -last(range(365 * 67)|("1970-03-01T01:02:03Z"|strptime("%Y-%m-%dT%H:%M:%SZ")|mktime) + (86400 * .)|strftime("%Y-%m-%dT%H:%M:%SZ")|strptime("%Y-%m-%dT%H:%M:%SZ")) +last(range(365 * 67)|("1970-03-01T01:02:03Z"|strptime("%Y-%m-%dT%H:%M:%SZ")|timegm) + (86400 * .)|strftime("%Y-%m-%dT%H:%M:%SZ")|strptime("%Y-%m-%dT%H:%M:%SZ")) null -[2037,1,11,1,2,3,3,41] +[2037,1,11,1,2,3,3,41,false,0] # %e is not available on mingw/WIN32 strftime("%A, %B %e, %Y") 1435677542.822351 "Tuesday, June 30, 2015" +strptime("%Y-%m-%dT%H:%M:%SZ")|mktime +"2015-03-05T23:51:47Z" +1425599507 diff --git a/tests/shtest b/tests/shtest index ee060e2e08..c037973293 100755 --- a/tests/shtest +++ b/tests/shtest @@ -579,4 +579,19 @@ if ( ! $msys && ! $mingw ) && locale -a > /dev/null; then fi fi +# #2863 +if ! TZ=CST+6CDT $VALGRIND $Q $JQ -cne ' + 1693243637 + | localtime + | (.[9] |= if .==null then -18000 else . end) + | (.[10] |= if .==null then "CDT" else . end) + | [.,mktime,timegm,timelocal,timegm - timelocal] + | debug + | (.[1] == .[2]) + and + (. == [[2023,7,28,12,27,17,1,239,true,-18000,"CDT"],1693225637,1693225637,1693243637,-18000])'; then + echo "Time functions not working correctly" + exit 1 +fi + exit 0