Add 'human' date format

This adds --date=human, which skips the timezone if it matches 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 really recent dates (same day), use the relative date stamp, while for old dates (year doesn't match), don't bother with time and timezone. Also add 'auto' date mode, which defaults to human if we're using the pager. So you can do git config --add log.date auto and your "git log" commands will show the human-legible format unless you're scripting things. Note that this time format still shows the timezone for recent enough events (but not so recent that they show up as relative dates). You can combine it with the "-local" suffix to never show timezones for an even more simplified view. Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org> Signed-off-by: Stephen P. Smith <ischis2@cox.net> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Linus Torvalds committed Jan 17, 2019 at 23:18 UTC acdd37769de8b0fe37a74bfc0475b63bdc55e9dc
3 files changed +115 -20
builtin/blame.c
+4
@@ -917,6 +917,10 @@ parse_done:
917 */
918 blame_date_width = utf8_strwidth(_("4 years, 11 months ago")) + 1; /* add the null */
919 break;
920 + case DATE_HUMAN:
921 + /* If the year is shown, no time is shown */
922 + blame_date_width = sizeof("Thu Oct 19 16:00");
923 + break;
924 case DATE_NORMAL:
925 blame_date_width = sizeof("Thu Oct 19 16:00:04 2006 -0700");
926 break;
cache.h
+1
@@ -1428,6 +1428,7 @@ extern struct object *peel_to_type(const char *name, int namelen,
1428 struct date_mode {
1429 enum date_mode_type {
1430 DATE_NORMAL = 0,
1431 + DATE_HUMAN,
1432 DATE_RELATIVE,
1433 DATE_SHORT,
1434 DATE_ISO8601,
date.c
+110 -20
@@ -77,22 +77,16 @@ static struct tm *time_to_tm_local(timestamp_t time)
77 }
78
79 /*
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.
82 */
83 -static int local_tzoffset(timestamp_t time)
83 +static int local_time_tzoffset(time_t t, struct tm *tm)
84 {
85 - time_t t, t_local;
86 - struct tm tm;
85 + time_t t_local;
86 int offset, eastwest;
87
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);
90 if (t_local == -1)
91 return 0; /* error; just use +0000 */
92 if (t_local < t) {
@@ -107,6 +101,20 @@ static int local_tzoffset(timestamp_t time)
101 return offset * eastwest;
102 }
103
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 void show_date_relative(timestamp_t time, int tz,
119 const struct timeval *now,
120 struct strbuf *timebuf)
@@ -191,9 +199,80 @@ struct date_mode *date_mode_from_type(enum date_mode_type type)
199 return &mode;
200 }
201
202 +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)
203 +{
204 + struct {
205 + unsigned int year:1,
206 + date:1,
207 + wday:1,
208 + time:1,
209 + seconds:1,
210 + tz:1;
211 + } hide = { 0 };
212 +
213 + hide.tz = local || tz == human_tz;
214 + hide.year = tm->tm_year == human_tm->tm_year;
215 + if (hide.year) {
216 + if (tm->tm_mon == human_tm->tm_mon) {
217 + if (tm->tm_mday > human_tm->tm_mday) {
218 + /* Future date: think timezones */
219 + } else if (tm->tm_mday == human_tm->tm_mday) {
220 + hide.date = hide.wday = 1;
221 + } else if (tm->tm_mday + 5 > human_tm->tm_mday) {
222 + /* Leave just weekday if it was a few days ago */
223 + hide.date = 1;
224 + }
225 + }
226 + }
227 +
228 + /* Show "today" times as just relative times */
229 + if (hide.wday) {
230 + struct timeval now;
231 + gettimeofday(&now, NULL);
232 + show_date_relative(time, tz, &now, buf);
233 + return;
234 + }
235 +
236 + /*
237 + * Always hide seconds for human-readable.
238 + * Hide timezone if showing date.
239 + * Hide weekday and time if showing year.
240 + *
241 + * The logic here is two-fold:
242 + * (a) only show details when recent enough to matter
243 + * (b) keep the maximum length "similar", and in check
244 + */
245 + if (human_tm->tm_year) {
246 + hide.seconds = 1;
247 + hide.tz |= !hide.date;
248 + hide.wday = hide.time = !hide.year;
249 + }
250 +
251 + if (!hide.wday)
252 + strbuf_addf(buf, "%.3s ", weekday_names[tm->tm_wday]);
253 + if (!hide.date)
254 + strbuf_addf(buf, "%.3s %d ", month_names[tm->tm_mon], tm->tm_mday);
255 +
256 + /* Do we want AM/PM depending on locale? */
257 + if (!hide.time) {
258 + strbuf_addf(buf, "%02d:%02d", tm->tm_hour, tm->tm_min);
259 + if (!hide.seconds)
260 + strbuf_addf(buf, ":%02d", tm->tm_sec);
261 + } else
262 + strbuf_rtrim(buf);
263 +
264 + if (!hide.year)
265 + strbuf_addf(buf, " %d", tm->tm_year + 1900);
266 +
267 + if (!hide.tz)
268 + strbuf_addf(buf, " %+05d", tz);
269 +}
270 +
271 const char *show_date(timestamp_t time, int tz, const struct date_mode *mode)
272 {
273 struct tm *tm;
274 + struct tm human_tm = { 0 };
275 + int human_tz = -1;
276 static struct strbuf timebuf = STRBUF_INIT;
277
278 if (mode->type == DATE_UNIX) {
@@ -202,6 +281,15 @@ const char *show_date(timestamp_t time, int tz, const struct date_mode *mode)
281 return timebuf.buf;
282 }
283
284 + if (mode->type == DATE_HUMAN) {
285 + struct timeval now;
286 +
287 + gettimeofday(&now, NULL);
288 +
289 + /* Fill in the data for "current time" in human_tz and human_tm */
290 + human_tz = local_time_tzoffset(now.tv_sec, &human_tm);
291 + }
292 +
293 if (mode->local)
294 tz = local_tzoffset(time);
295
@@ -258,14 +346,7 @@ const char *show_date(timestamp_t time, int tz, const struct date_mode *mode)
346 strbuf_addftime(&timebuf, mode->strftime_fmt, tm, tz,
347 !mode->local);
348 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);
349 + show_date_normal(&timebuf, time, tm, tz, &human_tm, human_tz, mode->local);
350 return timebuf.buf;
351 }
352
@@ -802,6 +883,11 @@ int parse_date(const char *date, struct strbuf *result)
883 return 0;
884 }
885
886 +static int auto_date_style(void)
887 +{
888 + return (isatty(1) || pager_in_use()) ? DATE_HUMAN : DATE_NORMAL;
889 +}
890 +
891 static enum date_mode_type parse_date_type(const char *format, const char **end)
892 {
893 if (skip_prefix(format, "relative", end))
@@ -819,6 +905,10 @@ static enum date_mode_type parse_date_type(const char *format, const char **end)
905 return DATE_SHORT;
906 if (skip_prefix(format, "default", end))
907 return DATE_NORMAL;
908 + if (skip_prefix(format, "human", end))
909 + return DATE_HUMAN;
910 + if (skip_prefix(format, "auto", end))
911 + return auto_date_style();
912 if (skip_prefix(format, "raw", end))
913 return DATE_RAW;
914 if (skip_prefix(format, "unix", end))