Merge branch 'lt/date-human'
authorJunio C Hamano <gitster@pobox.com>
Thu, 7 Feb 2019 06:05:24 +0000 (22:05 -0800)
committerJunio C Hamano <gitster@pobox.com>
Thu, 7 Feb 2019 06:05:24 +0000 (22:05 -0800)
A new date format "--date=human" that morphs its output depending
on how far the time is from the current time has been introduced.
"--date=auto" can be used to use this new format when the output is
going to the pager or to the terminal and otherwise the default
format.

* lt/date-human:
Add `human` date format tests.
Add `human` format to test-tool
Add 'human' date format documentation
Replace the proposed 'auto' mode with 'auto:'
Add 'human' date format

Documentation/git-log.txt
Documentation/rev-list-options.txt
builtin/blame.c
cache.h
date.c
t/helper/test-date.c
t/t0006-date.sh
index 90761f169444c165f0e94ebc3b7731cd8d85d3f0..b02e922dc33d248493df4bf6e1bb42f342e38daa 100644 (file)
@@ -192,6 +192,10 @@ log.date::
        Default format for human-readable dates.  (Compare the
        `--date` option.)  Defaults to "default", which means to write
        dates like `Sat May 8 19:35:34 2010 -0500`.
++
+If the format is set to "auto:foo" and the pager is in use, format
+"foo" will be the used for the date format. Otherwise "default" will
+be used.
 
 log.follow::
        If `true`, `git log` will act as if the `--follow` option was used when
index 8a4867998e0bf2c76953ca483773971b005a1479..cad711ce0ac060d9356e49599ffc13d130fc593c 100644 (file)
@@ -836,6 +836,13 @@ Note that the `-local` option does not affect the seconds-since-epoch
 value (which is always measured in UTC), but does switch the accompanying
 timezone value.
 +
+`--date=human` shows the timezone if the timezone does not match the
+current time-zone, and doesn't print the whole date if that matches
+(ie skip printing year for dates that are "this year", but also skip
+the whole date itself if it's in the last few days and we can just say
+what weekday it was).  For older dates the hour and minute is also
+omitted.
++
 `--date=unix` shows the date as a Unix epoch timestamp (seconds since
 1970).  As with `--raw`, this is always in UTC and therefore `-local`
 has no effect.
index 0074ed311c8b6c26770f62a668f51abda316ec46..581de0d8322681ef11026b3e36a81c7baf0fda38 100644 (file)
@@ -925,6 +925,10 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
                 */
                blame_date_width = utf8_strwidth(_("4 years, 11 months ago")) + 1; /* add the null */
                break;
+       case DATE_HUMAN:
+               /* If the year is shown, no time is shown */
+               blame_date_width = sizeof("Thu Oct 19 16:00");
+               break;
        case DATE_NORMAL:
                blame_date_width = sizeof("Thu Oct 19 16:00:04 2006 -0700");
                break;
diff --git a/cache.h b/cache.h
index 400bc0ab25cd5dd12a3b1a71aab0168259df6e19..ef9f3c4eaf2956c416b3b698ce965acb9c58ca98 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -1463,6 +1463,7 @@ extern struct object *peel_to_type(const char *name, int namelen,
 
 enum date_mode_type {
        DATE_NORMAL = 0,
+       DATE_HUMAN,
        DATE_RELATIVE,
        DATE_SHORT,
        DATE_ISO8601,
@@ -1490,6 +1491,8 @@ struct date_mode *date_mode_from_type(enum date_mode_type type);
 const char *show_date(timestamp_t time, int timezone, const struct date_mode *mode);
 void show_date_relative(timestamp_t time, const struct timeval *now,
                        struct strbuf *timebuf);
+void show_date_human(timestamp_t time, int tz, const struct timeval *now,
+                       struct strbuf *timebuf);
 int parse_date(const char *date, struct strbuf *out);
 int parse_date_basic(const char *date, timestamp_t *timestamp, int *offset);
 int parse_expiry_date(const char *date, timestamp_t *timestamp);
diff --git a/date.c b/date.c
index 61449f8b2e51507f148617120d455eefda19a406..9c5870e102951ed25f5def2103dab15bf8ed5c99 100644 (file)
--- a/date.c
+++ b/date.c
@@ -77,22 +77,16 @@ static struct tm *time_to_tm_local(timestamp_t time)
 }
 
 /*
- * What value of "tz" was in effect back then at "time" in the
- * local timezone?
+ * Fill in the localtime 'struct tm' for the supplied time,
+ * and return the local tz.
  */
-static int local_tzoffset(timestamp_t time)
+static int local_time_tzoffset(time_t t, struct tm *tm)
 {
-       time_t t, t_local;
-       struct tm tm;
+       time_t t_local;
        int offset, eastwest;
 
-       if (date_overflows(time))
-               die("Timestamp too large for this system: %"PRItime, time);
-
-       t = (time_t)time;
-       localtime_r(&t, &tm);
-       t_local = tm_to_time_t(&tm);
-
+       localtime_r(&t, tm);
+       t_local = tm_to_time_t(tm);
        if (t_local == -1)
                return 0; /* error; just use +0000 */
        if (t_local < t) {
@@ -107,6 +101,33 @@ static int local_tzoffset(timestamp_t time)
        return offset * eastwest;
 }
 
+/*
+ * What value of "tz" was in effect back then at "time" in the
+ * local timezone?
+ */
+static int local_tzoffset(timestamp_t time)
+{
+       struct tm tm;
+
+       if (date_overflows(time))
+               die("Timestamp too large for this system: %"PRItime, time);
+
+       return local_time_tzoffset((time_t)time, &tm);
+}
+
+static void get_time(struct timeval *now)
+{
+       const char *x;
+
+       x = getenv("GIT_TEST_DATE_NOW");
+       if (x) {
+               now->tv_sec = atoi(x);
+               now->tv_usec = 0;
+       }
+       else
+               gettimeofday(now, NULL);
+}
+
 void show_date_relative(timestamp_t time,
                        const struct timeval *now,
                        struct strbuf *timebuf)
@@ -191,9 +212,80 @@ struct date_mode *date_mode_from_type(enum date_mode_type type)
        return &mode;
 }
 
+static void show_date_normal(struct strbuf *buf, timestamp_t time, struct tm *tm, int tz, struct tm *human_tm, int human_tz, int local)
+{
+       struct {
+               unsigned int    year:1,
+                               date:1,
+                               wday:1,
+                               time:1,
+                               seconds:1,
+                               tz:1;
+       } hide = { 0 };
+
+       hide.tz = local || tz == human_tz;
+       hide.year = tm->tm_year == human_tm->tm_year;
+       if (hide.year) {
+               if (tm->tm_mon == human_tm->tm_mon) {
+                       if (tm->tm_mday > human_tm->tm_mday) {
+                               /* Future date: think timezones */
+                       } else if (tm->tm_mday == human_tm->tm_mday) {
+                               hide.date = hide.wday = 1;
+                       } else if (tm->tm_mday + 5 > human_tm->tm_mday) {
+                               /* Leave just weekday if it was a few days ago */
+                               hide.date = 1;
+                       }
+               }
+       }
+
+       /* Show "today" times as just relative times */
+       if (hide.wday) {
+               struct timeval now;
+               get_time(&now);
+               show_date_relative(time, &now, buf);
+               return;
+       }
+
+       /*
+        * Always hide seconds for human-readable.
+        * Hide timezone if showing date.
+        * Hide weekday and time if showing year.
+        *
+        * The logic here is two-fold:
+        *  (a) only show details when recent enough to matter
+        *  (b) keep the maximum length "similar", and in check
+        */
+       if (human_tm->tm_year) {
+               hide.seconds = 1;
+               hide.tz |= !hide.date;
+               hide.wday = hide.time = !hide.year;
+       }
+
+       if (!hide.wday)
+               strbuf_addf(buf, "%.3s ", weekday_names[tm->tm_wday]);
+       if (!hide.date)
+               strbuf_addf(buf, "%.3s %d ", month_names[tm->tm_mon], tm->tm_mday);
+
+       /* Do we want AM/PM depending on locale? */
+       if (!hide.time) {
+               strbuf_addf(buf, "%02d:%02d", tm->tm_hour, tm->tm_min);
+               if (!hide.seconds)
+                       strbuf_addf(buf, ":%02d", tm->tm_sec);
+       } else
+               strbuf_rtrim(buf);
+
+       if (!hide.year)
+               strbuf_addf(buf, " %d", tm->tm_year + 1900);
+
+       if (!hide.tz)
+               strbuf_addf(buf, " %+05d", tz);
+}
+
 const char *show_date(timestamp_t time, int tz, const struct date_mode *mode)
 {
        struct tm *tm;
+       struct tm human_tm = { 0 };
+       int human_tz = -1;
        static struct strbuf timebuf = STRBUF_INIT;
 
        if (mode->type == DATE_UNIX) {
@@ -202,6 +294,15 @@ const char *show_date(timestamp_t time, int tz, const struct date_mode *mode)
                return timebuf.buf;
        }
 
+       if (mode->type == DATE_HUMAN) {
+               struct timeval now;
+
+               get_time(&now);
+
+               /* Fill in the data for "current time" in human_tz and human_tm */
+               human_tz = local_time_tzoffset(now.tv_sec, &human_tm);
+       }
+
        if (mode->local)
                tz = local_tzoffset(time);
 
@@ -215,7 +316,7 @@ const char *show_date(timestamp_t time, int tz, const struct date_mode *mode)
                struct timeval now;
 
                strbuf_reset(&timebuf);
-               gettimeofday(&now, NULL);
+               get_time(&now);
                show_date_relative(time, &now, &timebuf);
                return timebuf.buf;
        }
@@ -258,14 +359,7 @@ const char *show_date(timestamp_t time, int tz, const struct date_mode *mode)
                strbuf_addftime(&timebuf, mode->strftime_fmt, tm, tz,
                                !mode->local);
        else
-               strbuf_addf(&timebuf, "%.3s %.3s %d %02d:%02d:%02d %d%c%+05d",
-                               weekday_names[tm->tm_wday],
-                               month_names[tm->tm_mon],
-                               tm->tm_mday,
-                               tm->tm_hour, tm->tm_min, tm->tm_sec,
-                               tm->tm_year + 1900,
-                               mode->local ? 0 : ' ',
-                               tz);
+               show_date_normal(&timebuf, time, tm, tz, &human_tm, human_tz, mode->local);
        return timebuf.buf;
 }
 
@@ -819,6 +913,8 @@ static enum date_mode_type parse_date_type(const char *format, const char **end)
                return DATE_SHORT;
        if (skip_prefix(format, "default", end))
                return DATE_NORMAL;
+       if (skip_prefix(format, "human", end))
+               return DATE_HUMAN;
        if (skip_prefix(format, "raw", end))
                return DATE_RAW;
        if (skip_prefix(format, "unix", end))
@@ -833,6 +929,14 @@ void parse_date_format(const char *format, struct date_mode *mode)
 {
        const char *p;
 
+       /* "auto:foo" is "if tty/pager, then foo, otherwise normal" */
+       if (skip_prefix(format, "auto:", &p)) {
+               if (isatty(1) || pager_in_use())
+                       format = p;
+               else
+                       format = "default";
+       }
+
        /* historical alias */
        if (!strcmp(format, "local"))
                format = "default-local";
@@ -1205,7 +1309,7 @@ timestamp_t approxidate_careful(const char *date, int *error_ret)
                return timestamp;
        }
 
-       gettimeofday(&tv, NULL);
+       get_time(&tv);
        return approxidate_str(date, &tv, error_ret);
 }
 
index aac4d542c2936bf90f5ef9c0f27ff347aaf8b660..a47bfa3003ba72d9ccac56981bf2ed89302af802 100644 (file)
@@ -3,6 +3,7 @@
 
 static const char *usage_msg = "\n"
 "  test-tool date relative [time_t]...\n"
+"  test-tool date human [time_t]...\n"
 "  test-tool date show:<format> [time_t]...\n"
 "  test-tool date parse [date]...\n"
 "  test-tool date approxidate [date]...\n"
@@ -22,6 +23,14 @@ static void show_relative_dates(const char **argv, struct timeval *now)
        strbuf_release(&buf);
 }
 
+static void show_human_dates(const char **argv)
+{
+       for (; *argv; argv++) {
+               time_t t = atoi(*argv);
+               printf("%s -> %s\n", *argv, show_date(t, 0, DATE_MODE(HUMAN)));
+       }
+}
+
 static void show_dates(const char **argv, const char *format)
 {
        struct date_mode mode;
@@ -87,7 +96,7 @@ int cmd__date(int argc, const char **argv)
        struct timeval now;
        const char *x;
 
-       x = getenv("TEST_DATE_NOW");
+       x = getenv("GIT_TEST_DATE_NOW");
        if (x) {
                now.tv_sec = atoi(x);
                now.tv_usec = 0;
@@ -100,6 +109,8 @@ int cmd__date(int argc, const char **argv)
                usage(usage_msg);
        if (!strcmp(*argv, "relative"))
                show_relative_dates(argv+1, &now);
+       else if (!strcmp(*argv, "human"))
+               show_human_dates(argv+1);
        else if (skip_prefix(*argv, "show:", &x))
                show_dates(argv+1, x);
        else if (!strcmp(*argv, "parse"))
index ffb2975e4875287bd510503a3becfd90fd7d325b..d9fcc829a9e6cb10088e6503841928cf85d1d697 100755 (executable)
@@ -4,10 +4,10 @@ test_description='test date parsing and printing'
 . ./test-lib.sh
 
 # arbitrary reference time: 2009-08-30 19:20:00
-TEST_DATE_NOW=1251660000; export TEST_DATE_NOW
+GIT_TEST_DATE_NOW=1251660000; export GIT_TEST_DATE_NOW
 
 check_relative() {
-       t=$(($TEST_DATE_NOW - $1))
+       t=$(($GIT_TEST_DATE_NOW - $1))
        echo "$t -> $2" >expect
        test_expect_${3:-success} "relative date ($2)" "
        test-tool date relative $t >actual &&
@@ -128,4 +128,22 @@ check_approxidate '6AM, June 7, 2009' '2009-06-07 06:00:00'
 check_approxidate '2008-12-01' '2008-12-01 19:20:00'
 check_approxidate '2009-12-01' '2009-12-01 19:20:00'
 
+check_date_format_human() {
+       t=$(($GIT_TEST_DATE_NOW - $1))
+       echo "$t -> $2" >expect
+       test_expect_success "human date $t" '
+               test-tool date human $t >actual &&
+               test_i18ncmp expect actual
+'
+}
+
+check_date_format_human 18000 "5 hours ago" # 5 hours ago
+check_date_format_human 432000 "Tue Aug 25 19:20" # 5 days ago
+check_date_format_human 1728000 "Mon Aug 10 19:20" # 3 weeks ago
+check_date_format_human 13000000 "Thu Apr 2 08:13" # 5 months ago
+check_date_format_human 31449600 "Aug 31 2008" # 12 months ago
+check_date_format_human 37500000 "Jun 22 2008" # 1 year, 2 months ago
+check_date_format_human 55188000 "Dec 1 2007" # 1 year, 9 months ago
+check_date_format_human 630000000 "Sep 13 1989" # 20 years ago
+
 test_done