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

1  2 
Documentation/rev-list-options.txt
date.c
t/t0006-date.sh
index 9c44eae55dbeac5fde111acf006bd0a4d586901e,3921066b7f4d3d654aa451ab767aad6795470598..a6cf9eb380fabedd0e89b84d182e6e2c4798e503
@@@ -91,14 -91,9 +91,14 @@@ endif::git-rev-list[
        Consider the limiting patterns to be fixed strings (don't interpret
        pattern as a regular expression).
  
 +-P::
  --perl-regexp::
 -      Consider the limiting patterns to be Perl-compatible regular expressions.
 -      Requires libpcre to be compiled in.
 +      Consider the limiting patterns to be Perl-compatible regular
 +      expressions.
 ++
 +Support for these types of regular expressions is an optional
 +compile-time dependency. If Git wasn't compiled with support for them
 +providing this option will cause it to die.
  
  --remove-empty::
        Stop when a given path disappears from the tree.
@@@ -769,7 -764,8 +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 --combined date.c
index 63fa99685e288bd79c75fd6af983f8b628a08fed,5580c40ac094211cb3d378b5b523a63308de0a2d..1fd6d663758de5a4b9d7f395b16716864317ed84
--- 1/date.c
--- 2/date.c
+++ b/date.c
@@@ -39,24 -39,14 +39,24 @@@ static const char *weekday_names[] = 
        "Sundays", "Mondays", "Tuesdays", "Wednesdays", "Thursdays", "Fridays", "Saturdays"
  };
  
 -static time_t gm_time_t(unsigned long time, int tz)
 +static time_t gm_time_t(timestamp_t time, int tz)
  {
        int minutes;
  
        minutes = tz < 0 ? -tz : tz;
        minutes = (minutes / 100)*60 + (minutes % 100);
        minutes = tz < 0 ? -minutes : minutes;
 -      return time + minutes * 60;
 +
 +      if (minutes > 0) {
 +              if (unsigned_add_overflows(time, minutes * 60))
 +                      die("Timestamp+tz too large: %"PRItime" +%04d",
 +                          time, tz);
 +      } else if (time < -minutes * 60)
 +              die("Timestamp before Unix epoch: %"PRItime" %04d", time, tz);
 +      time += minutes * 60;
 +      if (date_overflows(time))
 +              die("Timestamp too large for this system: %"PRItime, time);
 +      return (time_t)time;
  }
  
  /*
   * thing, which means that tz -0100 is passed in as the integer -100,
   * even though it means "sixty minutes off"
   */
 -static struct tm *time_to_tm(unsigned long time, int tz)
 +static struct tm *time_to_tm(timestamp_t time, int tz)
  {
        time_t t = gm_time_t(time, tz);
        return gmtime(&t);
  }
  
 -static struct tm *time_to_tm_local(unsigned long time)
++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?
   */
 -static int local_tzoffset(unsigned long time)
 +static int local_tzoffset(timestamp_t time)
  {
        time_t t, t_local;
        struct tm tm;
        int offset, eastwest;
  
 -      t = time;
 +      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);
  
        return offset * eastwest;
  }
  
 -void show_date_relative(unsigned long time, int tz,
 +void show_date_relative(timestamp_t time, int tz,
                               const struct timeval *now,
                               struct strbuf *timebuf)
  {
 -      unsigned long diff;
 +      timestamp_t diff;
        if (now->tv_sec < time) {
                strbuf_addstr(timebuf, _("in the future"));
                return;
        diff = now->tv_sec - time;
        if (diff < 90) {
                strbuf_addf(timebuf,
 -                       Q_("%lu second ago", "%lu seconds ago", diff), diff);
 +                       Q_("%"PRItime" second ago", "%"PRItime" seconds ago", diff), diff);
                return;
        }
        /* Turn it into minutes */
        diff = (diff + 30) / 60;
        if (diff < 90) {
                strbuf_addf(timebuf,
 -                       Q_("%lu minute ago", "%lu minutes ago", diff), diff);
 +                       Q_("%"PRItime" minute ago", "%"PRItime" minutes ago", diff), diff);
                return;
        }
        /* Turn it into hours */
        diff = (diff + 30) / 60;
        if (diff < 36) {
                strbuf_addf(timebuf,
 -                       Q_("%lu hour ago", "%lu hours ago", diff), diff);
 +                       Q_("%"PRItime" hour ago", "%"PRItime" hours ago", diff), diff);
                return;
        }
        /* We deal with number of days from here on */
        diff = (diff + 12) / 24;
        if (diff < 14) {
                strbuf_addf(timebuf,
 -                       Q_("%lu day ago", "%lu days ago", diff), diff);
 +                       Q_("%"PRItime" day ago", "%"PRItime" days ago", diff), diff);
                return;
        }
        /* Say weeks for the past 10 weeks or so */
        if (diff < 70) {
                strbuf_addf(timebuf,
 -                       Q_("%lu week ago", "%lu weeks ago", (diff + 3) / 7),
 +                       Q_("%"PRItime" week ago", "%"PRItime" weeks ago", (diff + 3) / 7),
                         (diff + 3) / 7);
                return;
        }
        /* Say months for the past 12 months or so */
        if (diff < 365) {
                strbuf_addf(timebuf,
 -                       Q_("%lu month ago", "%lu months ago", (diff + 15) / 30),
 +                       Q_("%"PRItime" month ago", "%"PRItime" months ago", (diff + 15) / 30),
                         (diff + 15) / 30);
                return;
        }
        /* Give years and months for 5 years or so */
        if (diff < 1825) {
 -              unsigned long totalmonths = (diff * 12 * 2 + 365) / (365 * 2);
 -              unsigned long years = totalmonths / 12;
 -              unsigned long months = totalmonths % 12;
 +              timestamp_t totalmonths = (diff * 12 * 2 + 365) / (365 * 2);
 +              timestamp_t years = totalmonths / 12;
 +              timestamp_t months = totalmonths % 12;
                if (months) {
                        struct strbuf sb = STRBUF_INIT;
 -                      strbuf_addf(&sb, Q_("%lu year", "%lu years", years), years);
 +                      strbuf_addf(&sb, Q_("%"PRItime" year", "%"PRItime" years", years), years);
                        strbuf_addf(timebuf,
                                 /* TRANSLATORS: "%s" is "<n> years" */
 -                               Q_("%s, %lu month ago", "%s, %lu months ago", months),
 +                               Q_("%s, %"PRItime" month ago", "%s, %"PRItime" months ago", months),
                                 sb.buf, months);
                        strbuf_release(&sb);
                } else
                        strbuf_addf(timebuf,
 -                               Q_("%lu year ago", "%lu years ago", years), years);
 +                               Q_("%"PRItime" year ago", "%"PRItime" years ago", years), years);
                return;
        }
        /* Otherwise, just years. Centuries is probably overkill. */
        strbuf_addf(timebuf,
 -               Q_("%lu year ago", "%lu years ago", (diff + 183) / 365),
 +               Q_("%"PRItime" year ago", "%"PRItime" years ago", (diff + 183) / 365),
                 (diff + 183) / 365);
  }
  
@@@ -185,14 -178,14 +191,14 @@@ struct date_mode *date_mode_from_type(e
        return &mode;
  }
  
 -const char *show_date(unsigned long time, int tz, const struct date_mode *mode)
 +const char *show_date(timestamp_t time, int tz, const struct date_mode *mode)
  {
        struct tm *tm;
        static struct strbuf timebuf = STRBUF_INIT;
  
        if (mode->type == DATE_UNIX) {
                strbuf_reset(&timebuf);
 -              strbuf_addf(&timebuf, "%lu", time);
 +              strbuf_addf(&timebuf, "%"PRItime, time);
                return timebuf.buf;
        }
  
  
        if (mode->type == DATE_RAW) {
                strbuf_reset(&timebuf);
 -              strbuf_addf(&timebuf, "%lu %+05d", time, tz);
 +              strbuf_addf(&timebuf, "%"PRItime" %+05d", time, tz);
                return timebuf.buf;
        }
  
                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;
                        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],
@@@ -438,7 -435,7 +448,7 @@@ static int is_date(int year, int month
        return 0;
  }
  
 -static int match_multi_number(unsigned long num, char c, const char *date,
 +static int match_multi_number(timestamp_t num, char c, const char *date,
                              char *end, struct tm *tm, time_t now)
  {
        struct tm now_tm;
@@@ -521,9 -518,9 +531,9 @@@ static int match_digit(const char *date
  {
        int n;
        char *end;
 -      unsigned long num;
 +      timestamp_t num;
  
 -      num = strtoul(date, &end, 10);
 +      num = parse_timestamp(date, &end, 10);
  
        /*
         * Seconds since 1970? We trigger on that for any numbers with
@@@ -648,7 -645,7 +658,7 @@@ static int match_tz(const char *date, i
        return end - date;
  }
  
 -static void date_string(unsigned long date, int offset, struct strbuf *buf)
 +static void date_string(timestamp_t date, int offset, struct strbuf *buf)
  {
        int sign = '+';
  
                offset = -offset;
                sign = '-';
        }
 -      strbuf_addf(buf, "%lu %c%02d%02d", date, sign, offset / 60, offset % 60);
 +      strbuf_addf(buf, "%"PRItime" %c%02d%02d", date, sign, offset / 60, offset % 60);
  }
  
  /*
   * Parse a string like "0 +0000" as ancient timestamp near epoch, but
   * only when it appears not as part of any other string.
   */
 -static int match_object_header_date(const char *date, unsigned long *timestamp, int *offset)
 +static int match_object_header_date(const char *date, timestamp_t *timestamp, int *offset)
  {
        char *end;
 -      unsigned long stamp;
 +      timestamp_t stamp;
        int ofs;
  
        if (*date < '0' || '9' < *date)
                return -1;
 -      stamp = strtoul(date, &end, 10);
 -      if (*end != ' ' || stamp == ULONG_MAX || (end[1] != '+' && end[1] != '-'))
 +      stamp = parse_timestamp(date, &end, 10);
 +      if (*end != ' ' || stamp == TIME_MAX || (end[1] != '+' && end[1] != '-'))
                return -1;
        date = end + 2;
        ofs = strtol(date, &end, 10);
  
  /* Gr. strptime is crap for this; it doesn't have a way to require RFC2822
     (i.e. English) day/month names, and it doesn't work correctly with %z. */
 -int parse_date_basic(const char *date, unsigned long *timestamp, int *offset)
 +int parse_date_basic(const char *date, timestamp_t *timestamp, int *offset)
  {
        struct tm tm;
        int tm_gmt;
 -      unsigned long dummy_timestamp;
 +      timestamp_t dummy_timestamp;
        int dummy_offset;
  
        if (!timestamp)
        return 0; /* success */
  }
  
 -int parse_expiry_date(const char *date, unsigned long *timestamp)
 +int parse_expiry_date(const char *date, timestamp_t *timestamp)
  {
        int errors = 0;
  
                 * of the past, and there is nothing from the future
                 * to be kept.
                 */
 -              *timestamp = ULONG_MAX;
 +              *timestamp = TIME_MAX;
        else
                *timestamp = approxidate_careful(date, &errors);
  
  
  int parse_date(const char *date, struct strbuf *result)
  {
 -      unsigned long timestamp;
 +      timestamp_t timestamp;
        int offset;
        if (parse_date_basic(date, &timestamp, &offset))
                return -1;
@@@ -858,7 -855,7 +868,7 @@@ void datestamp(struct strbuf *out
   * Relative time update (eg "2 days ago").  If we haven't set the time
   * yet, we need to set it from current time.
   */
 -static unsigned long update_tm(struct tm *tm, struct tm *now, unsigned long sec)
 +static time_t update_tm(struct tm *tm, struct tm *now, time_t sec)
  {
        time_t n;
  
@@@ -1079,7 -1076,7 +1089,7 @@@ static const char *approxidate_digit(co
                                     time_t now)
  {
        char *end;
 -      unsigned long number = strtoul(date, &end, 10);
 +      timestamp_t number = parse_timestamp(date, &end, 10);
  
        switch (*end) {
        case ':':
@@@ -1127,9 -1124,9 +1137,9 @@@ static void pending_number(struct tm *t
        }
  }
  
 -static unsigned long approxidate_str(const char *date,
 -                                   const struct timeval *tv,
 -                                   int *error_ret)
 +static timestamp_t approxidate_str(const char *date,
 +                                 const struct timeval *tv,
 +                                 int *error_ret)
  {
        int number = 0;
        int touched = 0;
        pending_number(&tm, &number);
        if (!touched)
                *error_ret = 1;
 -      return update_tm(&tm, &now, 0);
 +      return (timestamp_t)update_tm(&tm, &now, 0);
  }
  
 -unsigned long approxidate_relative(const char *date, const struct timeval *tv)
 +timestamp_t approxidate_relative(const char *date, const struct timeval *tv)
  {
 -      unsigned long timestamp;
 +      timestamp_t timestamp;
        int offset;
        int errors = 0;
  
        return approxidate_str(date, tv, &errors);
  }
  
 -unsigned long approxidate_careful(const char *date, int *error_ret)
 +timestamp_t approxidate_careful(const char *date, int *error_ret)
  {
        struct timeval tv;
 -      unsigned long timestamp;
 +      timestamp_t timestamp;
        int offset;
        int dummy = 0;
        if (!error_ret)
        return approxidate_str(date, &tv, error_ret);
  }
  
 -int date_overflows(unsigned long t)
 +int date_overflows(timestamp_t t)
  {
        time_t sys;
  
 -      /* If we overflowed our unsigned long, that's bad... */
 -      if (t == ULONG_MAX)
 +      /* If we overflowed our timestamp data type, that's bad... */
 +      if ((uintmax_t)t >= TIME_MAX)
                return 1;
  
        /*
diff --combined t/t0006-date.sh
index 42d4ea61ef531d8c495c5c2216d434982f1c6182,8e219b7c51fead82d81c049f8d1948016dc823e1..7ac9466d5055e02179467fa9e41004bbc89df6dc
@@@ -31,9 -31,11 +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,10 -53,20 +53,20 @@@ check_show iso-local "$TIME" '2016-06-1
  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" LONG_IS_64BIT
 -check_show iso-local "$FUTURE" "2152-06-19 22:24:56 +0000" LONG_IS_64BIT
 +check_show iso       "$FUTURE" "2152-06-19 18:24:56 -0400" TIME_IS_64BIT,TIME_T_IS_64BIT
 +check_show iso-local "$FUTURE" "2152-06-19 22:24:56 +0000" TIME_IS_64BIT,TIME_T_IS_64BIT
  
  check_parse() {
        echo "$1 -> $2" >expect