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 <u...@gentoo.org>
Helped-by: Jeff King <p...@peff.net>
Signed-off-by: Rene Scharfe <l....@web.de>
---
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

Reply via email to