Skip to content

Commit ecbe1be

Browse files
committed
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
2 parents 13e2630 + 110a6a1 commit ecbe1be

File tree

7 files changed

+176
-25
lines changed

7 files changed

+176
-25
lines changed

Documentation/git-log.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,10 @@ log.date::
192192
Default format for human-readable dates. (Compare the
193193
`--date` option.) Defaults to "default", which means to write
194194
dates like `Sat May 8 19:35:34 2010 -0500`.
195+
+
196+
If the format is set to "auto:foo" and the pager is in use, format
197+
"foo" will be the used for the date format. Otherwise "default" will
198+
be used.
195199

196200
log.follow::
197201
If `true`, `git log` will act as if the `--follow` option was used when

Documentation/rev-list-options.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -836,6 +836,13 @@ Note that the `-local` option does not affect the seconds-since-epoch
836836
value (which is always measured in UTC), but does switch the accompanying
837837
timezone value.
838838
+
839+
`--date=human` shows the timezone if the timezone does not match the
840+
current time-zone, and doesn't print the whole date if that matches
841+
(ie skip printing year for dates that are "this year", but also skip
842+
the whole date itself if it's in the last few days and we can just say
843+
what weekday it was). For older dates the hour and minute is also
844+
omitted.
845+
+
839846
`--date=unix` shows the date as a Unix epoch timestamp (seconds since
840847
1970). As with `--raw`, this is always in UTC and therefore `-local`
841848
has no effect.

builtin/blame.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -925,6 +925,10 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
925925
*/
926926
blame_date_width = utf8_strwidth(_("4 years, 11 months ago")) + 1; /* add the null */
927927
break;
928+
case DATE_HUMAN:
929+
/* If the year is shown, no time is shown */
930+
blame_date_width = sizeof("Thu Oct 19 16:00");
931+
break;
928932
case DATE_NORMAL:
929933
blame_date_width = sizeof("Thu Oct 19 16:00:04 2006 -0700");
930934
break;

cache.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1463,6 +1463,7 @@ extern struct object *peel_to_type(const char *name, int namelen,
14631463

14641464
enum date_mode_type {
14651465
DATE_NORMAL = 0,
1466+
DATE_HUMAN,
14661467
DATE_RELATIVE,
14671468
DATE_SHORT,
14681469
DATE_ISO8601,
@@ -1490,6 +1491,8 @@ struct date_mode *date_mode_from_type(enum date_mode_type type);
14901491
const char *show_date(timestamp_t time, int timezone, const struct date_mode *mode);
14911492
void show_date_relative(timestamp_t time, const struct timeval *now,
14921493
struct strbuf *timebuf);
1494+
void show_date_human(timestamp_t time, int tz, const struct timeval *now,
1495+
struct strbuf *timebuf);
14931496
int parse_date(const char *date, struct strbuf *out);
14941497
int parse_date_basic(const char *date, timestamp_t *timestamp, int *offset);
14951498
int parse_expiry_date(const char *date, timestamp_t *timestamp);

date.c

Lines changed: 126 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -77,22 +77,16 @@ static struct tm *time_to_tm_local(timestamp_t time)
7777
}
7878

7979
/*
80-
* What value of "tz" was in effect back then at "time" in the
81-
* local timezone?
80+
* Fill in the localtime 'struct tm' for the supplied time,
81+
* and return the local tz.
8282
*/
83-
static int local_tzoffset(timestamp_t time)
83+
static int local_time_tzoffset(time_t t, struct tm *tm)
8484
{
85-
time_t t, t_local;
86-
struct tm tm;
85+
time_t t_local;
8786
int offset, eastwest;
8887

89-
if (date_overflows(time))
90-
die("Timestamp too large for this system: %"PRItime, time);
91-
92-
t = (time_t)time;
93-
localtime_r(&t, &tm);
94-
t_local = tm_to_time_t(&tm);
95-
88+
localtime_r(&t, tm);
89+
t_local = tm_to_time_t(tm);
9690
if (t_local == -1)
9791
return 0; /* error; just use +0000 */
9892
if (t_local < t) {
@@ -107,6 +101,33 @@ static int local_tzoffset(timestamp_t time)
107101
return offset * eastwest;
108102
}
109103

104+
/*
105+
* What value of "tz" was in effect back then at "time" in the
106+
* local timezone?
107+
*/
108+
static int local_tzoffset(timestamp_t time)
109+
{
110+
struct tm tm;
111+
112+
if (date_overflows(time))
113+
die("Timestamp too large for this system: %"PRItime, time);
114+
115+
return local_time_tzoffset((time_t)time, &tm);
116+
}
117+
118+
static void get_time(struct timeval *now)
119+
{
120+
const char *x;
121+
122+
x = getenv("GIT_TEST_DATE_NOW");
123+
if (x) {
124+
now->tv_sec = atoi(x);
125+
now->tv_usec = 0;
126+
}
127+
else
128+
gettimeofday(now, NULL);
129+
}
130+
110131
void show_date_relative(timestamp_t time,
111132
const struct timeval *now,
112133
struct strbuf *timebuf)
@@ -191,9 +212,80 @@ struct date_mode *date_mode_from_type(enum date_mode_type type)
191212
return &mode;
192213
}
193214

215+
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)
216+
{
217+
struct {
218+
unsigned int year:1,
219+
date:1,
220+
wday:1,
221+
time:1,
222+
seconds:1,
223+
tz:1;
224+
} hide = { 0 };
225+
226+
hide.tz = local || tz == human_tz;
227+
hide.year = tm->tm_year == human_tm->tm_year;
228+
if (hide.year) {
229+
if (tm->tm_mon == human_tm->tm_mon) {
230+
if (tm->tm_mday > human_tm->tm_mday) {
231+
/* Future date: think timezones */
232+
} else if (tm->tm_mday == human_tm->tm_mday) {
233+
hide.date = hide.wday = 1;
234+
} else if (tm->tm_mday + 5 > human_tm->tm_mday) {
235+
/* Leave just weekday if it was a few days ago */
236+
hide.date = 1;
237+
}
238+
}
239+
}
240+
241+
/* Show "today" times as just relative times */
242+
if (hide.wday) {
243+
struct timeval now;
244+
get_time(&now);
245+
show_date_relative(time, &now, buf);
246+
return;
247+
}
248+
249+
/*
250+
* Always hide seconds for human-readable.
251+
* Hide timezone if showing date.
252+
* Hide weekday and time if showing year.
253+
*
254+
* The logic here is two-fold:
255+
* (a) only show details when recent enough to matter
256+
* (b) keep the maximum length "similar", and in check
257+
*/
258+
if (human_tm->tm_year) {
259+
hide.seconds = 1;
260+
hide.tz |= !hide.date;
261+
hide.wday = hide.time = !hide.year;
262+
}
263+
264+
if (!hide.wday)
265+
strbuf_addf(buf, "%.3s ", weekday_names[tm->tm_wday]);
266+
if (!hide.date)
267+
strbuf_addf(buf, "%.3s %d ", month_names[tm->tm_mon], tm->tm_mday);
268+
269+
/* Do we want AM/PM depending on locale? */
270+
if (!hide.time) {
271+
strbuf_addf(buf, "%02d:%02d", tm->tm_hour, tm->tm_min);
272+
if (!hide.seconds)
273+
strbuf_addf(buf, ":%02d", tm->tm_sec);
274+
} else
275+
strbuf_rtrim(buf);
276+
277+
if (!hide.year)
278+
strbuf_addf(buf, " %d", tm->tm_year + 1900);
279+
280+
if (!hide.tz)
281+
strbuf_addf(buf, " %+05d", tz);
282+
}
283+
194284
const char *show_date(timestamp_t time, int tz, const struct date_mode *mode)
195285
{
196286
struct tm *tm;
287+
struct tm human_tm = { 0 };
288+
int human_tz = -1;
197289
static struct strbuf timebuf = STRBUF_INIT;
198290

199291
if (mode->type == DATE_UNIX) {
@@ -202,6 +294,15 @@ const char *show_date(timestamp_t time, int tz, const struct date_mode *mode)
202294
return timebuf.buf;
203295
}
204296

297+
if (mode->type == DATE_HUMAN) {
298+
struct timeval now;
299+
300+
get_time(&now);
301+
302+
/* Fill in the data for "current time" in human_tz and human_tm */
303+
human_tz = local_time_tzoffset(now.tv_sec, &human_tm);
304+
}
305+
205306
if (mode->local)
206307
tz = local_tzoffset(time);
207308

@@ -215,7 +316,7 @@ const char *show_date(timestamp_t time, int tz, const struct date_mode *mode)
215316
struct timeval now;
216317

217318
strbuf_reset(&timebuf);
218-
gettimeofday(&now, NULL);
319+
get_time(&now);
219320
show_date_relative(time, &now, &timebuf);
220321
return timebuf.buf;
221322
}
@@ -258,14 +359,7 @@ const char *show_date(timestamp_t time, int tz, const struct date_mode *mode)
258359
strbuf_addftime(&timebuf, mode->strftime_fmt, tm, tz,
259360
!mode->local);
260361
else
261-
strbuf_addf(&timebuf, "%.3s %.3s %d %02d:%02d:%02d %d%c%+05d",
262-
weekday_names[tm->tm_wday],
263-
month_names[tm->tm_mon],
264-
tm->tm_mday,
265-
tm->tm_hour, tm->tm_min, tm->tm_sec,
266-
tm->tm_year + 1900,
267-
mode->local ? 0 : ' ',
268-
tz);
362+
show_date_normal(&timebuf, time, tm, tz, &human_tm, human_tz, mode->local);
269363
return timebuf.buf;
270364
}
271365

@@ -819,6 +913,8 @@ static enum date_mode_type parse_date_type(const char *format, const char **end)
819913
return DATE_SHORT;
820914
if (skip_prefix(format, "default", end))
821915
return DATE_NORMAL;
916+
if (skip_prefix(format, "human", end))
917+
return DATE_HUMAN;
822918
if (skip_prefix(format, "raw", end))
823919
return DATE_RAW;
824920
if (skip_prefix(format, "unix", end))
@@ -833,6 +929,14 @@ void parse_date_format(const char *format, struct date_mode *mode)
833929
{
834930
const char *p;
835931

932+
/* "auto:foo" is "if tty/pager, then foo, otherwise normal" */
933+
if (skip_prefix(format, "auto:", &p)) {
934+
if (isatty(1) || pager_in_use())
935+
format = p;
936+
else
937+
format = "default";
938+
}
939+
836940
/* historical alias */
837941
if (!strcmp(format, "local"))
838942
format = "default-local";
@@ -1205,7 +1309,7 @@ timestamp_t approxidate_careful(const char *date, int *error_ret)
12051309
return timestamp;
12061310
}
12071311

1208-
gettimeofday(&tv, NULL);
1312+
get_time(&tv);
12091313
return approxidate_str(date, &tv, error_ret);
12101314
}
12111315

t/helper/test-date.c

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
static const char *usage_msg = "\n"
55
" test-tool date relative [time_t]...\n"
6+
" test-tool date human [time_t]...\n"
67
" test-tool date show:<format> [time_t]...\n"
78
" test-tool date parse [date]...\n"
89
" test-tool date approxidate [date]...\n"
@@ -22,6 +23,14 @@ static void show_relative_dates(const char **argv, struct timeval *now)
2223
strbuf_release(&buf);
2324
}
2425

26+
static void show_human_dates(const char **argv)
27+
{
28+
for (; *argv; argv++) {
29+
time_t t = atoi(*argv);
30+
printf("%s -> %s\n", *argv, show_date(t, 0, DATE_MODE(HUMAN)));
31+
}
32+
}
33+
2534
static void show_dates(const char **argv, const char *format)
2635
{
2736
struct date_mode mode;
@@ -87,7 +96,7 @@ int cmd__date(int argc, const char **argv)
8796
struct timeval now;
8897
const char *x;
8998

90-
x = getenv("TEST_DATE_NOW");
99+
x = getenv("GIT_TEST_DATE_NOW");
91100
if (x) {
92101
now.tv_sec = atoi(x);
93102
now.tv_usec = 0;
@@ -100,6 +109,8 @@ int cmd__date(int argc, const char **argv)
100109
usage(usage_msg);
101110
if (!strcmp(*argv, "relative"))
102111
show_relative_dates(argv+1, &now);
112+
else if (!strcmp(*argv, "human"))
113+
show_human_dates(argv+1);
103114
else if (skip_prefix(*argv, "show:", &x))
104115
show_dates(argv+1, x);
105116
else if (!strcmp(*argv, "parse"))

t/t0006-date.sh

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@ test_description='test date parsing and printing'
44
. ./test-lib.sh
55

66
# arbitrary reference time: 2009-08-30 19:20:00
7-
TEST_DATE_NOW=1251660000; export TEST_DATE_NOW
7+
GIT_TEST_DATE_NOW=1251660000; export GIT_TEST_DATE_NOW
88

99
check_relative() {
10-
t=$(($TEST_DATE_NOW - $1))
10+
t=$(($GIT_TEST_DATE_NOW - $1))
1111
echo "$t -> $2" >expect
1212
test_expect_${3:-success} "relative date ($2)" "
1313
test-tool date relative $t >actual &&
@@ -128,4 +128,22 @@ check_approxidate '6AM, June 7, 2009' '2009-06-07 06:00:00'
128128
check_approxidate '2008-12-01' '2008-12-01 19:20:00'
129129
check_approxidate '2009-12-01' '2009-12-01 19:20:00'
130130

131+
check_date_format_human() {
132+
t=$(($GIT_TEST_DATE_NOW - $1))
133+
echo "$t -> $2" >expect
134+
test_expect_success "human date $t" '
135+
test-tool date human $t >actual &&
136+
test_i18ncmp expect actual
137+
'
138+
}
139+
140+
check_date_format_human 18000 "5 hours ago" # 5 hours ago
141+
check_date_format_human 432000 "Tue Aug 25 19:20" # 5 days ago
142+
check_date_format_human 1728000 "Mon Aug 10 19:20" # 3 weeks ago
143+
check_date_format_human 13000000 "Thu Apr 2 08:13" # 5 months ago
144+
check_date_format_human 31449600 "Aug 31 2008" # 12 months ago
145+
check_date_format_human 37500000 "Jun 22 2008" # 1 year, 2 months ago
146+
check_date_format_human 55188000 "Dec 1 2007" # 1 year, 9 months ago
147+
check_date_format_human 630000000 "Sep 13 1989" # 20 years ago
148+
131149
test_done

0 commit comments

Comments
 (0)