Merge branch 'rs/strbuf-addftime-zZ'
authorJunio C Hamano <gitster@pobox.com>
Thu, 22 Jun 2017 21:15:25 +0000 (14:15 -0700)
committerJunio C Hamano <gitster@pobox.com>
Thu, 22 Jun 2017 21:15:25 +0000 (14:15 -0700)
As there is no portable way to pass timezone information to
strftime, some output format from "git log" and friends are
impossible to produce. Teach our own strbuf_addftime to replace %z
and %Z with caller-supplied values to help working around this.

* rs/strbuf-addftime-zZ:
date: use localtime() for "-local" time formats
t0006: check --date=format zone offsets
strbuf: let strbuf_addftime handle %z and %Z itself

Documentation/rev-list-options.txt
date.c
strbuf.c
strbuf.h
t/t0006-date.sh
index 9c44eae55dbeac5fde111acf006bd0a4d586901e..a6cf9eb380fabedd0e89b84d182e6e2c4798e503 100644 (file)
@@ -769,7 +769,8 @@ timezone value.
 1970).  As with `--raw`, this is always in UTC and therefore `-local`
 has no effect.
 +
-`--date=format:...` feeds the format `...` to your system `strftime`.
+`--date=format:...` feeds the format `...` to your system `strftime`,
+except for %z and %Z, which are handled internally.
 Use `--date=format:%c` to show the date in your system locale's
 preferred format.  See the `strftime` manual for a complete list of
 format placeholders. When using `-local`, the correct syntax is
diff --git a/date.c b/date.c
index 63fa99685e288bd79c75fd6af983f8b628a08fed..1fd6d663758de5a4b9d7f395b16716864317ed84 100644 (file)
--- a/date.c
+++ b/date.c
@@ -70,6 +70,12 @@ static struct tm *time_to_tm(timestamp_t time, int tz)
        return gmtime(&t);
 }
 
+static struct tm *time_to_tm_local(timestamp_t time)
+{
+       time_t t = time;
+       return localtime(&t);
+}
+
 /*
  * What value of "tz" was in effect back then at "time" in the
  * local timezone?
@@ -214,7 +220,10 @@ const char *show_date(timestamp_t time, int tz, const struct date_mode *mode)
                return timebuf.buf;
        }
 
-       tm = time_to_tm(time, tz);
+       if (mode->local)
+               tm = time_to_tm_local(time);
+       else
+               tm = time_to_tm(time, tz);
        if (!tm) {
                tm = time_to_tm(0, 0);
                tz = 0;
@@ -246,7 +255,8 @@ 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,
+                               mode->local ? NULL : "");
        else
                strbuf_addf(&timebuf, "%.3s %.3s %d %02d:%02d:%02d %d%c%+05d",
                                weekday_names[tm->tm_wday],
index 00457940cfc163fed58b0206f5167c25c2c71b8a..be3b9e37b1d969e86e913cc69ccab935f516a063 100644 (file)
--- a/strbuf.c
+++ b/strbuf.c
@@ -785,14 +785,48 @@ 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.
+        */
+       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':
+                       if (tz_name) {
+                               strbuf_addstr(&munged_fmt, tz_name);
+                               fmt++;
+                               break;
+                       }
+                       /* FALLTHROUGH */
+               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 +838,16 @@ 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);
+               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);
 }
 
index 80047b1bb7b826699deff3e8c2590a2a00a5c121..4559035c47268c0603119b90c7278c08c6b99a09 100644 (file)
--- a/strbuf.h
+++ b/strbuf.h
@@ -340,8 +340,14 @@ 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);
+ * `tz_name` is used to expand %Z internally unless it's NULL.
+ * `tz_offset` is in decimal hhmm format, e.g. -600 means six hours west
+ * of Greenwich, and it's used to expand %z internally.  However, tokens
+ * with modifiers (e.g. %Ez) are passed to `strftime`.
+ */
+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.
index 42d4ea61ef531d8c495c5c2216d434982f1c6182..7ac9466d5055e02179467fa9e41004bbc89df6dc 100755 (executable)
@@ -31,9 +31,11 @@ check_show () {
        format=$1
        time=$2
        expect=$3
-       test_expect_success $4 "show date ($format:$time)" '
+       prereqs=$4
+       zone=$5
+       test_expect_success $prereqs "show date ($format:$time)" '
                echo "$time -> $expect" >expect &&
-               test-date show:$format "$time" >actual &&
+               TZ=${zone:-$TZ} test-date show:"$format" "$time" >actual &&
                test_cmp expect actual
        '
 }
@@ -51,6 +53,16 @@ check_show iso-local "$TIME" '2016-06-15 14:13:20 +0000'
 check_show raw-local "$TIME" '1466000000 +0000'
 check_show unix-local "$TIME" '1466000000'
 
+check_show 'format:%z' "$TIME" '+0200'
+check_show 'format-local:%z' "$TIME" '+0000'
+check_show 'format:%Z' "$TIME" ''
+check_show 'format-local:%Z' "$TIME" 'UTC'
+check_show 'format:%%z' "$TIME" '%z'
+check_show 'format-local:%%z' "$TIME" '%z'
+
+check_show 'format:%Y-%m-%d %H:%M:%S' "$TIME" '2016-06-15 16:13:20'
+check_show 'format-local:%Y-%m-%d %H:%M:%S' "$TIME" '2016-06-15 09:13:20' '' EST5
+
 # arbitrary time absurdly far in the future
 FUTURE="5758122296 -0400"
 check_show iso       "$FUTURE" "2152-06-19 18:24:56 -0400" TIME_IS_64BIT,TIME_T_IS_64BIT