date: use localtime() for "-local" time formats
authorJeff King <peff@peff.net>
Thu, 15 Jun 2017 13:52:17 +0000 (09:52 -0400)
committerJunio C Hamano <gitster@pobox.com>
Thu, 15 Jun 2017 21:39:15 +0000 (14:39 -0700)
When we convert seconds-since-epochs timestamps into a
broken-down "struct tm", we do so by adjusting the timestamp
according to the known offset and then using gmtime() to
break down the result. This means that the resulting struct
"knows" that it's in GMT, even though the time it represents
is adjusted for a different zone. The fields where it stores
this data are not portably accessible, so we have no way to
override them to tell them the real zone info.

For the most part, this works. Our date-formatting routines
don't pay attention to these inaccessible fields, and use
the same tz info we provided for adjustment. The one
exception is when we call strftime(), whose %Z format
reveals this hidden timezone data.

We solved that by always showing the empty string for %Z.
This is allowed by POSIX, but not very helpful to the user.
We can't make this work in the general case, as there's no
portable function for setting an arbitrary timezone (and
anyway, we don't have the zone name for the author zones,
only their offsets).

But for the special case of the "-local" formats, we can
just skip the adjustment and use localtime() instead of
gmtime(). This makes --date=format-local:%Z work correctly,
showing the local timezone instead of an empty string.

The new test checks the result for "UTC", our default
test-lib value for $TZ. Using something like EST5 might be
more interesting, but the actual zone string is
system-dependent (for instance, on my system it expands to
just EST). Hopefully "UTC" is vanilla enough that every
system treats it the same.

Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
date.c
t/t0006-date.sh

diff --git a/date.c b/date.c
index 6eccbd85465761b51fc10b60bf411e2e0c9f530e..5580c40ac094211cb3d378b5b523a63308de0a2d 100644 (file)
--- a/date.c
+++ b/date.c
@@ -60,6 +60,12 @@ static struct tm *time_to_tm(unsigned long time, int tz)
        return gmtime(&t);
 }
 
+static struct tm *time_to_tm_local(unsigned long time)
+{
+       time_t t = time;
+       return localtime(&t);
+}
+
 /*
  * What value of "tz" was in effect back then at "time" in the
  * local timezone?
@@ -201,7 +207,10 @@ const char *show_date(unsigned long 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;
@@ -233,7 +242,8 @@ const char *show_date(unsigned long 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, tz, "");
+               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 4b789c00f1227ecd35b8ce500441ca9a4982b56f..8e219b7c51fead82d81c049f8d1948016dc823e1 100755 (executable)
@@ -56,6 +56,7 @@ 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'