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:
Junio C Hamano
2019-02-06 22:05:24 -08:00
7 changed files with 176 additions and 25 deletions

148
date.c
View File

@ -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);
}