Skip to content

Commit e0ca192

Browse files
committed
Broken down time needs tm_{isdst,gmtoff,zone} too (fix jqlang#1912)
1 parent 91d7257 commit e0ca192

8 files changed

Lines changed: 122 additions & 56 deletions

File tree

configure.ac

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -145,9 +145,12 @@ AC_FIND_FUNC([gmtime], [c], [#include <time.h>], [0])
145145
AC_FIND_FUNC([localtime_r], [c], [#include <time.h>], [0, 0])
146146
AC_FIND_FUNC([localtime], [c], [#include <time.h>], [0])
147147
AC_FIND_FUNC([gettimeofday], [c], [#include <sys/time.h>], [0, 0])
148-
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])],
148+
AC_STRUCT_TIMEZONE
149+
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])],
149150
[], [[#include <time.h>]])
150-
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])],
151+
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])],
152+
[], [[#include <time.h>]])
153+
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])],
151154
[], [[#include <time.h>]])
152155

153156
dnl Figure out if we have the pthread functions we actually need

docs/content/manual/manual.yml

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2199,9 +2199,13 @@ sections:
21992199
(in this order): the year, the month (zero-based), the day of
22002200
the month (one-based), the hour of the day, the minute of the
22012201
hour, the second of the minute, the day of the week, and the
2202-
day of the year -- all one-based unless otherwise stated. The
2203-
day of the week number may be wrong on some systems for dates
2204-
before March 1st 1900, or after December 31 2099.
2202+
day of the year -all one-based unless otherwise stated-
2203+
followed by (depending on platform support) a boolean
2204+
indicating whether the time is in a daylight savings
2205+
timezone, followed by the offset to UTC, followed by the
2206+
name of the timezone. The day of the week number may be
2207+
wrong on some systems for dates before March 1st 1900, or
2208+
after December 31 2099.
22052209
22062210
The `localtime` builtin works like the `gmtime` builtin, but
22072211
using the local timezone setting.
@@ -2230,7 +2234,7 @@ sections:
22302234
input: '"2015-03-05T23:51:47Z"'
22312235
output: ['1425599507']
22322236

2233-
- program: 'strptime("%Y-%m-%dT%H:%M:%SZ")'
2237+
- program: 'strptime("%Y-%m-%dT%H:%M:%SZ")[0:8]'
22342238
input: '"2015-03-05T23:51:47Z"'
22352239
output: ['[2015,2,5,23,51,47,4,63]']
22362240

jq.1.prebuilt

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/builtin.c

Lines changed: 66 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1287,14 +1287,28 @@ static jv f_stderr(jq_state *jq, jv input) {
12871287
}
12881288

12891289
static jv tm2jv(struct tm *tm) {
1290-
return JV_ARRAY(jv_number(tm->tm_year + 1900),
1290+
jv a = JV_ARRAY(jv_number(tm->tm_year + 1900),
12911291
jv_number(tm->tm_mon),
12921292
jv_number(tm->tm_mday),
12931293
jv_number(tm->tm_hour),
12941294
jv_number(tm->tm_min),
12951295
jv_number(tm->tm_sec),
12961296
jv_number(tm->tm_wday),
1297-
jv_number(tm->tm_yday));
1297+
jv_number(tm->tm_yday),
1298+
tm->tm_isdst ? jv_true() : jv_false());
1299+
// JV_ARRAY() only handles up to 9 arguments
1300+
#ifdef HAVE_TM_TM_GMTOFF
1301+
a = jv_array_append(a, jv_number(tm->tm_gmtoff));
1302+
#else
1303+
a = jv_array_append(a, jv_null());
1304+
#endif
1305+
#ifdef HAVE_STRUCT_TM_TM_ZONE
1306+
if (tm->tm_zone)
1307+
return jv_array_append(a, jv_string(tm->tm_zone));
1308+
#else
1309+
a = jv_array_append(a, jv_null());
1310+
#endif
1311+
return a;
12981312
}
12991313

13001314
#if defined(WIN32) && !defined(HAVE_SETENV)
@@ -1488,7 +1502,10 @@ static jv f_strptime(jq_state *jq, jv a, jv b) {
14881502
jv_free(n); \
14891503
} while (0)
14901504

1491-
static int jv2tm(jv a, struct tm *tm) {
1505+
static int jv2tm(jv a, struct tm *tm, char **freeme) {
1506+
int ret = 1;
1507+
1508+
*freeme = NULL;
14921509
memset(tm, 0, sizeof(*tm));
14931510
TO_TM_FIELD(tm->tm_year, a, 0);
14941511
tm->tm_year -= 1900;
@@ -1499,6 +1516,34 @@ static int jv2tm(jv a, struct tm *tm) {
14991516
TO_TM_FIELD(tm->tm_sec, a, 5);
15001517
TO_TM_FIELD(tm->tm_wday, a, 6);
15011518
TO_TM_FIELD(tm->tm_yday, a, 7);
1519+
jv v = jv_array_get(jv_copy(a), 8);
1520+
switch (jv_get_kind(v)) {
1521+
case JV_KIND_INVALID: break;
1522+
case JV_KIND_FALSE: break;
1523+
case JV_KIND_TRUE: tm->tm_isdst = 1; break;
1524+
case JV_KIND_NUMBER: tm->tm_isdst = !!(int)jv_number_value(v); break;
1525+
default: ret = 0; break;
1526+
}
1527+
jv_free(v);
1528+
#ifdef HAVE_TM_TM_GMTOFF
1529+
v = jv_array_get(jv_copy(a), 9);
1530+
if (jv_get_kind(v) == JV_KIND_NUMBER)
1531+
tm->tm_gmtoff = jv_number_value(v);
1532+
else if (jv_is_valid(v))
1533+
ret = 0;
1534+
jv_free(v);
1535+
#endif
1536+
#ifdef HAVE_STRUCT_TM_TM_ZONE
1537+
v = jv_array_get(jv_copy(a), 10);
1538+
if (jv_get_kind(v) == JV_KIND_STRING) {
1539+
tm->tm_zone = *freeme = strdup(jv_string_value(v));
1540+
if (tm->tm_zone == NULL)
1541+
ret = 0;
1542+
} else if (jv_is_valid(v)) {
1543+
ret = 0;
1544+
}
1545+
#endif
1546+
jv_free(v);
15021547
jv_free(a);
15031548

15041549
// We use UTC everywhere (gettimeofday, gmtime) and UTC does not do DST.
@@ -1509,7 +1554,11 @@ static int jv2tm(jv a, struct tm *tm) {
15091554
// hope it is okay to initialize them to zero, because the standard does not
15101555
// provide an alternative.
15111556

1512-
return 1;
1557+
if (!ret) {
1558+
free(*freeme);
1559+
*freeme = NULL;
1560+
}
1561+
return ret;
15131562
}
15141563

15151564
#undef TO_TM_FIELD
@@ -1520,9 +1569,11 @@ static jv f_mktime(jq_state *jq, jv a) {
15201569
if (jv_array_length(jv_copy(a)) < 6)
15211570
return ret_error(a, jv_string("mktime requires parsed datetime inputs"));
15221571
struct tm tm;
1523-
if (!jv2tm(a, &tm))
1572+
char *freeme;
1573+
if (!jv2tm(a, &tm, &freeme))
15241574
return jv_invalid_with_msg(jv_string("mktime requires parsed datetime inputs"));
15251575
time_t t = my_mktime(&tm);
1576+
free(freeme);
15261577
if (t == (time_t)-1)
15271578
return jv_invalid_with_msg(jv_string("invalid gmtime representation"));
15281579
if (t == (time_t)-2)
@@ -1618,14 +1669,16 @@ static jv f_strftime(jq_state *jq, jv a, jv b) {
16181669
return ret_error2(a, b, jv_string("strftime/1 requires a string format"));
16191670
}
16201671
struct tm tm;
1621-
if (!jv2tm(a, &tm))
1672+
char *freeme;
1673+
if (!jv2tm(a, &tm, &freeme))
16221674
return ret_error(b, jv_string("strftime/1 requires parsed datetime inputs"));
16231675

16241676
const char *fmt = jv_string_value(b);
16251677
size_t alloced = strlen(fmt) + 100;
16261678
char *buf = alloca(alloced);
16271679
size_t n = strftime(buf, alloced, fmt, &tm);
16281680
jv_free(b);
1681+
free(freeme);
16291682
/* POSIX doesn't provide errno values for strftime() failures; weird */
16301683
if (n == 0 || n > alloced)
16311684
return jv_invalid_with_msg(jv_string("strftime/1: unknown system failure"));
@@ -1643,19 +1696,25 @@ static jv f_strftime(jq_state *jq, jv a, jv b) {
16431696
static jv f_strflocaltime(jq_state *jq, jv a, jv b) {
16441697
if (jv_get_kind(a) == JV_KIND_NUMBER) {
16451698
a = f_localtime(jq, a);
1699+
if (!jv_is_valid(a)) {
1700+
jv_free(b);
1701+
return a;
1702+
}
16461703
} else if (jv_get_kind(a) != JV_KIND_ARRAY) {
16471704
return ret_error2(a, b, jv_string("strflocaltime/1 requires parsed datetime inputs"));
16481705
} else if (jv_get_kind(b) != JV_KIND_STRING) {
16491706
return ret_error2(a, b, jv_string("strflocaltime/1 requires a string format"));
16501707
}
16511708
struct tm tm;
1652-
if (!jv2tm(a, &tm))
1709+
char *freeme;
1710+
if (!jv2tm(a, &tm, &freeme))
16531711
return ret_error(b, jv_string("strflocaltime/1 requires parsed datetime inputs"));
16541712
const char *fmt = jv_string_value(b);
16551713
size_t alloced = strlen(fmt) + 100;
16561714
char *buf = alloca(alloced);
16571715
size_t n = strftime(buf, alloced, fmt, &tm);
16581716
jv_free(b);
1717+
free(freeme);
16591718
/* POSIX doesn't provide errno values for strftime() failures; weird */
16601719
if (n == 0 || n > alloced)
16611720
return jv_invalid_with_msg(jv_string("strflocaltime/1: unknown system failure"));

src/util.c

Lines changed: 36 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -550,7 +550,7 @@ static const unsigned char *find_string(const unsigned char *, int *, const char
550550
#define strncasecmp _strnicmp
551551
#endif
552552

553-
#ifdef TM_ZONE
553+
#ifdef HAVE_STRUCT_TM_TM_ZONE
554554
static char* utc = "UTC";
555555
#endif
556556
/* RFC-822/RFC-2822 */
@@ -630,12 +630,12 @@ fromzone(const unsigned char **bp, struct tm *tm, int mandatory)
630630

631631
*bp = rp;
632632
tm->tm_isdst = 0; /* XXX */
633-
#ifdef TM_GMTOFF
634-
tm->TM_GMTOFF = tzgetgmtoff(tz, tm->tm_isdst);
633+
#ifdef HAVE_TM_TM_GMTOFF
634+
tm->tm_gmtoff = tzgetgmtoff(tz, tm->tm_isdst);
635635
#endif
636-
#ifdef TM_ZONE
636+
#ifdef HAVE_STRUCT_TM_TM_ZONE
637637
// Can't use tzgetname() here because we are going to free()
638-
tm->TM_ZONE = NULL; /* XXX */
638+
tm->tm_zone = NULL; /* XXX */
639639
#endif
640640
// tzfree(tz);
641641
return 1;
@@ -993,11 +993,11 @@ again: switch (c = *fmt++) {
993993
if (!delim(*bp))
994994
goto namedzone;
995995
tm->tm_isdst = 0;
996-
#ifdef TM_GMTOFF
997-
tm->TM_GMTOFF = 0;
996+
#ifdef HAVE_TM_TM_GMTOFF
997+
tm->tm_gmtoff = 0;
998998
#endif
999-
#ifdef TM_ZONE
1000-
tm->TM_ZONE = utc;
999+
#ifdef HAVE_STRUCT_TM_TM_ZONE
1000+
tm->tm_zone = utc;
10011001
#endif
10021002
continue;
10031003
case '+':
@@ -1014,30 +1014,30 @@ again: switch (c = *fmt++) {
10141014
if (delim(bp[1]) &&
10151015
((*bp >= 'A' && *bp <= 'I') ||
10161016
(*bp >= 'L' && *bp <= 'Y'))) {
1017-
#ifdef TM_GMTOFF
1017+
#ifdef HAVE_TM_TM_GMTOFF
10181018
/* Argh! No 'J'! */
10191019
if (*bp >= 'A' && *bp <= 'I')
1020-
tm->TM_GMTOFF =
1020+
tm->tm_gmtoff =
10211021
(int)*bp - ('A' - 1);
10221022
else if (*bp >= 'L' && *bp <= 'M')
1023-
tm->TM_GMTOFF = (int)*bp - 'A';
1023+
tm->tm_gmtoff = (int)*bp - 'A';
10241024
else if (*bp >= 'N' && *bp <= 'Y')
1025-
tm->TM_GMTOFF = 'M' - (int)*bp;
1026-
tm->TM_GMTOFF *= SECSPERHOUR;
1025+
tm->tm_gmtoff = 'M' - (int)*bp;
1026+
tm->tm_gmtoff *= SECSPERHOUR;
10271027
#endif
1028-
#ifdef TM_ZONE
1029-
tm->TM_ZONE = NULL; /* XXX */
1028+
#ifdef HAVE_STRUCT_TM_TM_ZONE
1029+
tm->tm_zone = NULL; /* XXX */
10301030
#endif
10311031
bp++;
10321032
continue;
10331033
}
10341034
/* 'J' is local time */
10351035
if (delim(bp[1]) && *bp == 'J') {
1036-
#ifdef TM_GMTOFF
1037-
tm->TM_GMTOFF = -timezone;
1036+
#ifdef HAVE_TM_TM_GMTOFF
1037+
tm->tm_gmtoff = -timezone;
10381038
#endif
1039-
#ifdef TM_ZONE
1040-
tm->TM_ZONE = NULL; /* XXX */
1039+
#ifdef HAVE_STRUCT_TM_TM_ZONE
1040+
tm->tm_zone = NULL; /* XXX */
10411041
#endif
10421042
bp++;
10431043
continue;
@@ -1052,23 +1052,23 @@ again: switch (c = *fmt++) {
10521052
goto loadzone;
10531053
ep = find_string(bp, &i, nast, NULL, 4);
10541054
if (ep != NULL) {
1055-
#ifdef TM_GMTOFF
1056-
tm->TM_GMTOFF = (-5 - i) * SECSPERHOUR;
1055+
#ifdef HAVE_TM_TM_GMTOFF
1056+
tm->tm_gmtoff = (-5 - i) * SECSPERHOUR;
10571057
#endif
1058-
#ifdef TM_ZONE
1059-
tm->TM_ZONE = __UNCONST(nast[i]);
1058+
#ifdef HAVE_STRUCT_TM_TM_ZONE
1059+
tm->tm_zone = __UNCONST(nast[i]);
10601060
#endif
10611061
bp = ep;
10621062
continue;
10631063
}
10641064
ep = find_string(bp, &i, nadt, NULL, 4);
10651065
if (ep != NULL) {
10661066
tm->tm_isdst = 1;
1067-
#ifdef TM_GMTOFF
1068-
tm->TM_GMTOFF = (-4 - i) * SECSPERHOUR;
1067+
#ifdef HAVE_TM_TM_GMTOFF
1068+
tm->tm_gmtoff = (-4 - i) * SECSPERHOUR;
10691069
#endif
1070-
#ifdef TM_ZONE
1071-
tm->TM_ZONE = __UNCONST(nadt[i]);
1070+
#ifdef HAVE_STRUCT_TM_TM_ZONE
1071+
tm->tm_zone = __UNCONST(nadt[i]);
10721072
#endif
10731073
bp = ep;
10741074
continue;
@@ -1081,11 +1081,11 @@ again: switch (c = *fmt++) {
10811081
NULL, 2);
10821082
if (ep != NULL) {
10831083
tm->tm_isdst = i;
1084-
#ifdef TM_GMTOFF
1085-
tm->TM_GMTOFF = -timezone;
1084+
#ifdef HAVE_TM_TM_GMTOFF
1085+
tm->tm_gmtoff = -timezone;
10861086
#endif
1087-
#ifdef TM_ZONE
1088-
tm->TM_ZONE = tzname[i];
1087+
#ifdef HAVE_STRUCT_TM_TM_ZONE
1088+
tm->tm_zone = tzname[i];
10891089
#endif
10901090
bp = ep;
10911091
continue;
@@ -1138,11 +1138,11 @@ again: switch (c = *fmt++) {
11381138
if (neg)
11391139
offs = -offs;
11401140
tm->tm_isdst = 0; /* XXX */
1141-
#ifdef TM_GMTOFF
1142-
tm->TM_GMTOFF = offs;
1141+
#ifdef HAVE_TM_TM_GMTOFF
1142+
tm->tm_gmtoff = offs;
11431143
#endif
1144-
#ifdef TM_ZONE
1145-
tm->TM_ZONE = NULL; /* XXX */
1144+
#ifdef HAVE_STRUCT_TM_TM_ZONE
1145+
tm->tm_zone = NULL; /* XXX */
11461146
#endif
11471147
continue;
11481148

tests/jq.test

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1563,9 +1563,9 @@ strftime("%A, %B %d, %Y")
15631563
1435677542.822351
15641564
"Tuesday, June 30, 2015"
15651565

1566-
gmtime
1566+
gmtime|(.[9] |= if .==null then 0 else . end)|(.[10] |= if (.=="UTC") or (.==null) then "GMT" else . end)
15671567
1425599507
1568-
[2015,2,5,23,51,47,4,63]
1568+
[2015,2,5,23,51,47,4,63,false,0,"GMT"]
15691569

15701570
# test invalid tm input
15711571
try strftime("%Y-%m-%dT%H:%M:%SZ") catch .

tests/man.test

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/optional.test

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,14 @@
33
# strptime() is not available on mingw/WIN32
44
[strptime("%Y-%m-%dT%H:%M:%SZ")|(.,mktime)]
55
"2015-03-05T23:51:47Z"
6-
[[2015,2,5,23,51,47,4,63],1425599507]
6+
[[2015,2,5,23,51,47,4,63,false,0],1425599507]
77

88
# Check day-of-week and day of year computations
99
# (should trip an assert if this fails)
1010
# This date range
1111
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"))
1212
null
13-
[2037,1,11,1,2,3,3,41]
13+
[2037,1,11,1,2,3,3,41,false,0]
1414

1515
# %e is not available on mingw/WIN32
1616
strftime("%A, %B %e, %Y")

0 commit comments

Comments
 (0)