Skip to content

Commit

Permalink
`mktime/0 returns GMT time instead of local time (fix #2865)
Browse files Browse the repository at this point in the history
  • Loading branch information
nicowilliams committed Aug 28, 2023
1 parent 3d3fef0 commit a5b1f48
Show file tree
Hide file tree
Showing 7 changed files with 77 additions and 59 deletions.
4 changes: 4 additions & 0 deletions configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -140,10 +140,14 @@ AC_FIND_FUNC([strptime], [c], [#include <time.h>], [0, 0, 0])
AC_FIND_FUNC([strftime], [c], [#include <time.h>], [0, 0, 0, 0])
AC_FIND_FUNC([setenv], [c], [#include <stdlib.h>], [0, 0, 0])
AC_FIND_FUNC([timegm], [c], [#include <time.h>], [0])
AC_FIND_FUNC([timelocal], [c], [#include <time.h>], [0])
AC_FIND_FUNC([gmtime_r], [c], [#include <time.h>], [0, 0])
AC_FIND_FUNC([gmtime], [c], [#include <time.h>], [0])
AC_FIND_FUNC([localtime_r], [c], [#include <time.h>], [0, 0])
AC_FIND_FUNC([localtime], [c], [#include <time.h>], [0])
AC_FIND_FUNC([mktime], [c], [#include <time.h>], [0])
AC_FIND_FUNC([_mkgmtime], [c], [#include <time.h>], [0])
AC_FIND_FUNC([difftime], [c], [#include <time.h>], [0, 0])
AC_FIND_FUNC([gettimeofday], [c], [#include <sys/time.h>], [0, 0])
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])],
Expand Down
15 changes: 9 additions & 6 deletions docs/content/manual/manual.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2187,11 +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
functionality.
`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
Unix epoch and outputs a "broken down time" representation of
Expand All @@ -2210,6 +2210,9 @@ sections:
The `localtime` builtin works like the `gmtime` builtin, but
using the local timezone setting.
The `timegm` and `timelocal` builtins do the opposite of
`gmtime` and `localtime`, respectively.
The `mktime` builtin consumes "broken down time"
representations of time output by `gmtime` and `strptime`.
Expand Down Expand Up @@ -2238,7 +2241,7 @@ sections:
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']

Expand Down
7 changes: 5 additions & 2 deletions jq.1.prebuilt

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

102 changes: 55 additions & 47 deletions src/builtin.c
Original file line number Diff line number Diff line change
Expand Up @@ -1332,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) {
/*
Expand Down Expand Up @@ -1571,14 +1527,22 @@ static int jv2tm(jv a, struct tm *tm, double *frac, char **freeme) {
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;
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 timelocal() if available, else mktime().
*/
#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"));
Expand All @@ -1587,6 +1551,48 @@ static jv f_mktime(jq_state *jq, jv a) {
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("tmigm 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
static jv f_gmtime(jq_state *jq, jv a) {
if (jv_get_kind(a) != JV_KIND_NUMBER)
Expand Down Expand Up @@ -1843,6 +1849,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},
Expand Down
2 changes: 1 addition & 1 deletion src/builtin.jq
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion tests/man.test

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions tests/optional.test
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
# 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")|(.,timegm)]
"2015-03-05T23:51:47Z"
[[2015,2,5,23,51,47,4,63,false,0],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,false,0]

Expand Down

0 comments on commit a5b1f48

Please sign in to comment.