Merge branch 'lt/date-human'
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
This commit is contained in:
commit
ecbe1beb8e
@ -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
|
||||
|
@ -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.
|
||||
|
@ -925,6 +925,10 @@ parse_done:
|
||||
*/
|
||||
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;
|
||||
|
3
cache.h
3
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);
|
||||
|
148
date.c
148
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);
|
||||
}
|
||||
|
||||
|
@ -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"))
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user