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:
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);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user