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 formatmaint
commit
ecbe1beb8e
|
@ -192,6 +192,10 @@ log.date::
|
||||||
Default format for human-readable dates. (Compare the
|
Default format for human-readable dates. (Compare the
|
||||||
`--date` option.) Defaults to "default", which means to write
|
`--date` option.) Defaults to "default", which means to write
|
||||||
dates like `Sat May 8 19:35:34 2010 -0500`.
|
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::
|
log.follow::
|
||||||
If `true`, `git log` will act as if the `--follow` option was used when
|
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
|
value (which is always measured in UTC), but does switch the accompanying
|
||||||
timezone value.
|
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
|
`--date=unix` shows the date as a Unix epoch timestamp (seconds since
|
||||||
1970). As with `--raw`, this is always in UTC and therefore `-local`
|
1970). As with `--raw`, this is always in UTC and therefore `-local`
|
||||||
has no effect.
|
has no effect.
|
||||||
|
|
|
@ -925,6 +925,10 @@ parse_done:
|
||||||
*/
|
*/
|
||||||
blame_date_width = utf8_strwidth(_("4 years, 11 months ago")) + 1; /* add the null */
|
blame_date_width = utf8_strwidth(_("4 years, 11 months ago")) + 1; /* add the null */
|
||||||
break;
|
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:
|
case DATE_NORMAL:
|
||||||
blame_date_width = sizeof("Thu Oct 19 16:00:04 2006 -0700");
|
blame_date_width = sizeof("Thu Oct 19 16:00:04 2006 -0700");
|
||||||
break;
|
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 {
|
enum date_mode_type {
|
||||||
DATE_NORMAL = 0,
|
DATE_NORMAL = 0,
|
||||||
|
DATE_HUMAN,
|
||||||
DATE_RELATIVE,
|
DATE_RELATIVE,
|
||||||
DATE_SHORT,
|
DATE_SHORT,
|
||||||
DATE_ISO8601,
|
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);
|
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,
|
void show_date_relative(timestamp_t time, const struct timeval *now,
|
||||||
struct strbuf *timebuf);
|
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(const char *date, struct strbuf *out);
|
||||||
int parse_date_basic(const char *date, timestamp_t *timestamp, int *offset);
|
int parse_date_basic(const char *date, timestamp_t *timestamp, int *offset);
|
||||||
int parse_expiry_date(const char *date, timestamp_t *timestamp);
|
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
|
* Fill in the localtime 'struct tm' for the supplied time,
|
||||||
* local timezone?
|
* 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;
|
time_t t_local;
|
||||||
struct tm tm;
|
|
||||||
int offset, eastwest;
|
int offset, eastwest;
|
||||||
|
|
||||||
if (date_overflows(time))
|
localtime_r(&t, tm);
|
||||||
die("Timestamp too large for this system: %"PRItime, time);
|
t_local = tm_to_time_t(tm);
|
||||||
|
|
||||||
t = (time_t)time;
|
|
||||||
localtime_r(&t, &tm);
|
|
||||||
t_local = tm_to_time_t(&tm);
|
|
||||||
|
|
||||||
if (t_local == -1)
|
if (t_local == -1)
|
||||||
return 0; /* error; just use +0000 */
|
return 0; /* error; just use +0000 */
|
||||||
if (t_local < t) {
|
if (t_local < t) {
|
||||||
|
@ -107,6 +101,33 @@ static int local_tzoffset(timestamp_t time)
|
||||||
return offset * eastwest;
|
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,
|
void show_date_relative(timestamp_t time,
|
||||||
const struct timeval *now,
|
const struct timeval *now,
|
||||||
struct strbuf *timebuf)
|
struct strbuf *timebuf)
|
||||||
|
@ -191,9 +212,80 @@ struct date_mode *date_mode_from_type(enum date_mode_type type)
|
||||||
return &mode;
|
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)
|
const char *show_date(timestamp_t time, int tz, const struct date_mode *mode)
|
||||||
{
|
{
|
||||||
struct tm *tm;
|
struct tm *tm;
|
||||||
|
struct tm human_tm = { 0 };
|
||||||
|
int human_tz = -1;
|
||||||
static struct strbuf timebuf = STRBUF_INIT;
|
static struct strbuf timebuf = STRBUF_INIT;
|
||||||
|
|
||||||
if (mode->type == DATE_UNIX) {
|
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;
|
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)
|
if (mode->local)
|
||||||
tz = local_tzoffset(time);
|
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;
|
struct timeval now;
|
||||||
|
|
||||||
strbuf_reset(&timebuf);
|
strbuf_reset(&timebuf);
|
||||||
gettimeofday(&now, NULL);
|
get_time(&now);
|
||||||
show_date_relative(time, &now, &timebuf);
|
show_date_relative(time, &now, &timebuf);
|
||||||
return timebuf.buf;
|
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,
|
strbuf_addftime(&timebuf, mode->strftime_fmt, tm, tz,
|
||||||
!mode->local);
|
!mode->local);
|
||||||
else
|
else
|
||||||
strbuf_addf(&timebuf, "%.3s %.3s %d %02d:%02d:%02d %d%c%+05d",
|
show_date_normal(&timebuf, time, tm, tz, &human_tm, human_tz, mode->local);
|
||||||
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);
|
|
||||||
return timebuf.buf;
|
return timebuf.buf;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -819,6 +913,8 @@ static enum date_mode_type parse_date_type(const char *format, const char **end)
|
||||||
return DATE_SHORT;
|
return DATE_SHORT;
|
||||||
if (skip_prefix(format, "default", end))
|
if (skip_prefix(format, "default", end))
|
||||||
return DATE_NORMAL;
|
return DATE_NORMAL;
|
||||||
|
if (skip_prefix(format, "human", end))
|
||||||
|
return DATE_HUMAN;
|
||||||
if (skip_prefix(format, "raw", end))
|
if (skip_prefix(format, "raw", end))
|
||||||
return DATE_RAW;
|
return DATE_RAW;
|
||||||
if (skip_prefix(format, "unix", end))
|
if (skip_prefix(format, "unix", end))
|
||||||
|
@ -833,6 +929,14 @@ void parse_date_format(const char *format, struct date_mode *mode)
|
||||||
{
|
{
|
||||||
const char *p;
|
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 */
|
/* historical alias */
|
||||||
if (!strcmp(format, "local"))
|
if (!strcmp(format, "local"))
|
||||||
format = "default-local";
|
format = "default-local";
|
||||||
|
@ -1205,7 +1309,7 @@ timestamp_t approxidate_careful(const char *date, int *error_ret)
|
||||||
return timestamp;
|
return timestamp;
|
||||||
}
|
}
|
||||||
|
|
||||||
gettimeofday(&tv, NULL);
|
get_time(&tv);
|
||||||
return approxidate_str(date, &tv, error_ret);
|
return approxidate_str(date, &tv, error_ret);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
static const char *usage_msg = "\n"
|
static const char *usage_msg = "\n"
|
||||||
" test-tool date relative [time_t]...\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 show:<format> [time_t]...\n"
|
||||||
" test-tool date parse [date]...\n"
|
" test-tool date parse [date]...\n"
|
||||||
" test-tool date approxidate [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);
|
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)
|
static void show_dates(const char **argv, const char *format)
|
||||||
{
|
{
|
||||||
struct date_mode mode;
|
struct date_mode mode;
|
||||||
|
@ -87,7 +96,7 @@ int cmd__date(int argc, const char **argv)
|
||||||
struct timeval now;
|
struct timeval now;
|
||||||
const char *x;
|
const char *x;
|
||||||
|
|
||||||
x = getenv("TEST_DATE_NOW");
|
x = getenv("GIT_TEST_DATE_NOW");
|
||||||
if (x) {
|
if (x) {
|
||||||
now.tv_sec = atoi(x);
|
now.tv_sec = atoi(x);
|
||||||
now.tv_usec = 0;
|
now.tv_usec = 0;
|
||||||
|
@ -100,6 +109,8 @@ int cmd__date(int argc, const char **argv)
|
||||||
usage(usage_msg);
|
usage(usage_msg);
|
||||||
if (!strcmp(*argv, "relative"))
|
if (!strcmp(*argv, "relative"))
|
||||||
show_relative_dates(argv+1, &now);
|
show_relative_dates(argv+1, &now);
|
||||||
|
else if (!strcmp(*argv, "human"))
|
||||||
|
show_human_dates(argv+1);
|
||||||
else if (skip_prefix(*argv, "show:", &x))
|
else if (skip_prefix(*argv, "show:", &x))
|
||||||
show_dates(argv+1, x);
|
show_dates(argv+1, x);
|
||||||
else if (!strcmp(*argv, "parse"))
|
else if (!strcmp(*argv, "parse"))
|
||||||
|
|
|
@ -4,10 +4,10 @@ test_description='test date parsing and printing'
|
||||||
. ./test-lib.sh
|
. ./test-lib.sh
|
||||||
|
|
||||||
# arbitrary reference time: 2009-08-30 19:20:00
|
# 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() {
|
check_relative() {
|
||||||
t=$(($TEST_DATE_NOW - $1))
|
t=$(($GIT_TEST_DATE_NOW - $1))
|
||||||
echo "$t -> $2" >expect
|
echo "$t -> $2" >expect
|
||||||
test_expect_${3:-success} "relative date ($2)" "
|
test_expect_${3:-success} "relative date ($2)" "
|
||||||
test-tool date relative $t >actual &&
|
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 '2008-12-01' '2008-12-01 19:20:00'
|
||||||
check_approxidate '2009-12-01' '2009-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
|
test_done
|
||||||
|
|
Loading…
Reference in New Issue