There is no portable way to pass timezone information to strftime. Add parameters for timezone offset and name to strbuf_addftime and let it handle the timezone-related format specifiers %z and %Z internally. Callers can opt out by passing NULL as timezone name. Use an empty string as timezone name in show_date (the only current caller) for now because we only have the timezone offset in non-local mode. POSIX allows %Z to resolve to nothing in case of missing info. Helped-by: Ulrich Mueller <ulm@xxxxxxxxxx> Helped-by: Jeff King <peff@xxxxxxxx> Signed-off-by: Rene Scharfe <l.s.r@xxxxxx> --- Duplicates strbuf_expand to a certain extent, but not too badly, I think. Leaves the door open for letting strftime handle the local case. date.c | 2 +- strbuf.c | 42 ++++++++++++++++++++++++++++++++++++++---- strbuf.h | 11 ++++++++--- t/t6006-rev-list-format.sh | 12 ++++++++++++ 4 files changed, 59 insertions(+), 8 deletions(-) diff --git a/date.c b/date.c index 63fa99685e..5580577334 100644 --- a/date.c +++ b/date.c @@ -246,7 +246,7 @@ const char *show_date(timestamp_t time, int tz, const struct date_mode *mode) month_names[tm->tm_mon], tm->tm_year + 1900, tm->tm_hour, tm->tm_min, tm->tm_sec, tz); else if (mode->type == DATE_STRFTIME) - strbuf_addftime(&timebuf, mode->strftime_fmt, tm); + strbuf_addftime(&timebuf, mode->strftime_fmt, tm, tz, ""); else strbuf_addf(&timebuf, "%.3s %.3s %d %02d:%02d:%02d %d%c%+05d", weekday_names[tm->tm_wday], diff --git a/strbuf.c b/strbuf.c index 00457940cf..b880107370 100644 --- a/strbuf.c +++ b/strbuf.c @@ -785,14 +785,47 @@ char *xstrfmt(const char *fmt, ...) return ret; } -void strbuf_addftime(struct strbuf *sb, const char *fmt, const struct tm *tm) +void strbuf_addftime(struct strbuf *sb, const char *fmt, const struct tm *tm, + int tz_offset, const char *tz_name) { + struct strbuf munged_fmt = STRBUF_INIT; size_t hint = 128; size_t len; if (!*fmt) return; + /* + * There is no portable way to pass timezone information to + * strftime, so we handle %z and %Z here. + */ + if (tz_name) { + for (;;) { + const char *percent = strchrnul(fmt, '%'); + strbuf_add(&munged_fmt, fmt, percent - fmt); + if (!*percent) + break; + fmt = percent + 1; + switch (*fmt) { + case '%': + strbuf_addstr(&munged_fmt, "%%"); + fmt++; + break; + case 'z': + strbuf_addf(&munged_fmt, "%+05d", tz_offset); + fmt++; + break; + case 'Z': + strbuf_addstr(&munged_fmt, tz_name); + fmt++; + break; + default: + strbuf_addch(&munged_fmt, '%'); + } + } + fmt = munged_fmt.buf; + } + strbuf_grow(sb, hint); len = strftime(sb->buf + sb->len, sb->alloc - sb->len, fmt, tm); @@ -804,17 +837,18 @@ void strbuf_addftime(struct strbuf *sb, const char *fmt, const struct tm *tm) * output contains at least one character, and then drop the extra * character before returning. */ - struct strbuf munged_fmt = STRBUF_INIT; - strbuf_addf(&munged_fmt, "%s ", fmt); + if (fmt != munged_fmt.buf) + strbuf_addstr(&munged_fmt, fmt); + strbuf_addch(&munged_fmt, ' '); while (!len) { hint *= 2; strbuf_grow(sb, hint); len = strftime(sb->buf + sb->len, sb->alloc - sb->len, munged_fmt.buf, tm); } - strbuf_release(&munged_fmt); len--; /* drop munged space */ } + strbuf_release(&munged_fmt); strbuf_setlen(sb, sb->len + len); } diff --git a/strbuf.h b/strbuf.h index 80047b1bb7..39d5836abd 100644 --- a/strbuf.h +++ b/strbuf.h @@ -339,9 +339,14 @@ __attribute__((format (printf,2,0))) extern void strbuf_vaddf(struct strbuf *sb, const char *fmt, va_list ap); /** - * Add the time specified by `tm`, as formatted by `strftime`. - */ -extern void strbuf_addftime(struct strbuf *sb, const char *fmt, const struct tm *tm); + * Add the time specified by `tm`, as formatted by `strftime`. `tz_offset` + * and `tz_name` are used to expand %z and %Z internally, unless `tz_name` + * is NULL. `tz_offset` is in decimal hhmm format, e.g. -600 means six + * hours west of Greenwich. + */ +extern void strbuf_addftime(struct strbuf *sb, const char *fmt, + const struct tm *tm, int tz_offset, + const char *tz_name); /** * Read a given size of data from a FILE* pointer to the buffer. diff --git a/t/t6006-rev-list-format.sh b/t/t6006-rev-list-format.sh index a1dcdb81d7..dc0bed8d90 100755 --- a/t/t6006-rev-list-format.sh +++ b/t/t6006-rev-list-format.sh @@ -483,4 +483,16 @@ test_expect_success 'unused %G placeholders are passed through' ' test_cmp expect actual ' +test_expect_success 'date format "%F %T %z" is the same as iso' ' + git log -1 --format="%ad" --date=iso >expect && + git log -1 --format="%ad" --date="format:%F %T %z" >actual && + test_cmp expect actual +' + +test_expect_success 'date format "%%z" expands to percent zed' ' + echo "%z" >expect && + git log -1 --format="%ad" --date="format:%%z" >actual && + test_cmp expect actual +' + test_done -- 2.13.0