Skip to content

Commit

Permalink
Broken down time needs tm_{isdst,gmtoff,zone} too (fix jqlang#1912)
Browse files Browse the repository at this point in the history
  • Loading branch information
nicowilliams committed Sep 7, 2023
1 parent 4b579ca commit b7e8510
Show file tree
Hide file tree
Showing 8 changed files with 122 additions and 56 deletions.
7 changes: 5 additions & 2 deletions configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -145,9 +145,12 @@ 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([gettimeofday], [c], [#include <sys/time.h>], [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 <time.h>]])
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 <time.h>]])
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 <time.h>]])
AC_FIND_FUNC([setlocale], [c], [#include <locale.h>], [0,0])

Expand Down
12 changes: 8 additions & 4 deletions docs/content/manual/manual.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2199,9 +2199,13 @@ 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.
Expand Down Expand Up @@ -2230,7 +2234,7 @@ 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]']

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

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

73 changes: 66 additions & 7 deletions src/builtin.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -1488,7 +1502,10 @@ static jv f_strptime(jq_state *jq, jv a, jv b) {
jv_free(n); \
} while (0)

static int jv2tm(jv a, struct tm *tm) {
static int jv2tm(jv a, struct tm *tm, char **freeme) {
int ret = 1;

*freeme = NULL;
memset(tm, 0, sizeof(*tm));
TO_TM_FIELD(tm->tm_year, a, 0);
tm->tm_year -= 1900;
Expand All @@ -1499,6 +1516,34 @@ static int jv2tm(jv a, struct tm *tm) {
TO_TM_FIELD(tm->tm_sec, a, 5);
TO_TM_FIELD(tm->tm_wday, a, 6);
TO_TM_FIELD(tm->tm_yday, a, 7);
jv v = jv_array_get(jv_copy(a), 8);
switch (jv_get_kind(v)) {
case JV_KIND_INVALID: break;
case JV_KIND_FALSE: break;
case JV_KIND_TRUE: tm->tm_isdst = 1; break;
case JV_KIND_NUMBER: tm->tm_isdst = !!(int)jv_number_value(v); break;
default: ret = 0; break;
}
jv_free(v);
#ifdef HAVE_TM_TM_GMTOFF
v = jv_array_get(jv_copy(a), 9);
if (jv_get_kind(v) == JV_KIND_NUMBER)
tm->tm_gmtoff = jv_number_value(v);
else if (jv_is_valid(v))
ret = 0;
jv_free(v);
#endif
#ifdef HAVE_STRUCT_TM_TM_ZONE
v = jv_array_get(jv_copy(a), 10);
if (jv_get_kind(v) == JV_KIND_STRING) {
tm->tm_zone = *freeme = strdup(jv_string_value(v));
if (tm->tm_zone == NULL)
ret = 0;
} else if (jv_is_valid(v)) {
ret = 0;
}
#endif
jv_free(v);
jv_free(a);

// We use UTC everywhere (gettimeofday, gmtime) and UTC does not do DST.
Expand All @@ -1509,7 +1554,11 @@ 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
Expand All @@ -1520,9 +1569,11 @@ static jv f_mktime(jq_state *jq, jv a) {
if (jv_array_length(jv_copy(a)) < 6)
return ret_error(a, jv_string("mktime requires parsed datetime inputs"));
struct tm tm;
if (!jv2tm(a, &tm))
char *freeme;
if (!jv2tm(a, &tm, &freeme))
return jv_invalid_with_msg(jv_string("mktime requires parsed datetime inputs"));
time_t t = my_mktime(&tm);
free(freeme);
if (t == (time_t)-1)
return jv_invalid_with_msg(jv_string("invalid gmtime representation"));
if (t == (time_t)-2)
Expand Down Expand Up @@ -1618,14 +1669,16 @@ 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, &freeme))
return ret_error(b, jv_string("strftime/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("strftime/1: unknown system failure"));
Expand All @@ -1643,19 +1696,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, &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"));
Expand Down
72 changes: 36 additions & 36 deletions src/util.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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 '+':
Expand All @@ -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;
Expand All @@ -1052,23 +1052,23 @@ 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;
}
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;
Expand All @@ -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;
Expand Down Expand Up @@ -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;

Expand Down
4 changes: 2 additions & 2 deletions tests/jq.test
Original file line number Diff line number Diff line change
Expand Up @@ -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 .
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
Expand Up @@ -3,14 +3,14 @@
# strptime() is not available on mingw/WIN32
[strptime("%Y-%m-%dT%H:%M:%SZ")|(.,mktime)]
"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]

# 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"))
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")
Expand Down

0 comments on commit b7e8510

Please sign in to comment.