Skip to content

Commit

Permalink
fix: populate timezone data when formatting time
Browse files Browse the repository at this point in the history
The `jv2tm` function was zeroing fields of `struct tm` that were not
specified by the standard. However, depending on the libc this produced
incorrect timezone data when used together with formatting functions.
This change tries to fill the timezone data using either `mktime`,
`timegm`, or manually.
  • Loading branch information
marcin-serwin committed Nov 14, 2024
1 parent a7b2253 commit e3873ef
Showing 1 changed file with 22 additions and 10 deletions.
32 changes: 22 additions & 10 deletions src/builtin.c
Original file line number Diff line number Diff line change
Expand Up @@ -1555,7 +1555,7 @@ static jv f_strptime(jq_state *jq, jv a, jv b) {
return r;
}

static int jv2tm(jv a, struct tm *tm) {
static int jv2tm(jv a, struct tm *tm, int localtime) {
memset(tm, 0, sizeof(*tm));
static const size_t offsets[] = {
offsetof(struct tm, tm_year),
Expand Down Expand Up @@ -1585,13 +1585,25 @@ static int jv2tm(jv a, struct tm *tm) {
jv_free(n);
}

// We use UTC everywhere (gettimeofday, gmtime) and UTC does not do DST.
// Setting tm_isdst to 0 is done by the memset.
// tm->tm_isdst = 0;
if (localtime) {
tm->tm_isdst = -1;
mktime(tm);
} else {
#ifdef HAVE_TIMEGM
timegm(tm);
#elif HAVE_TM_TM_GMT_OFF
// tm->tm_gmtoff = 0;
tm->tm_zone = "GMT";
#elif HAVE_TM___TM_GMT_OFF
// tm->__tm_gmtoff = 0;
tm->__tm_zone = "GMT";
#endif
// tm->tm_isdst = 0;

// The standard permits the tm structure to contain additional members. We
// hope it is okay to initialize them to zero, because the standard does not
// provide an alternative.
// The standard permits the tm structure to contain additional members. We
// hope it is okay to initialize them to zero, because the standard does not
// provide an alternative.
}

jv_free(a);
return 1;
Expand All @@ -1603,7 +1615,7 @@ 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"));
struct tm tm;
if (!jv2tm(a, &tm))
if (!jv2tm(a, &tm, 0))
return jv_invalid_with_msg(jv_string("mktime requires parsed datetime inputs"));
time_t t = my_mktime(&tm);
if (t == (time_t)-1)
Expand Down Expand Up @@ -1701,7 +1713,7 @@ static jv f_strftime(jq_state *jq, jv a, jv b) {
if (jv_get_kind(b) != JV_KIND_STRING)
return ret_error2(a, b, jv_string("strftime/1 requires a string format"));
struct tm tm;
if (!jv2tm(a, &tm))
if (!jv2tm(a, &tm, 0))
return ret_error(b, jv_string("strftime/1 requires parsed datetime inputs"));

const char *fmt = jv_string_value(b);
Expand Down Expand Up @@ -1732,7 +1744,7 @@ static jv f_strflocaltime(jq_state *jq, jv a, jv b) {
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))
if (!jv2tm(a, &tm, 1))
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;
Expand Down

0 comments on commit e3873ef

Please sign in to comment.