summaryrefslogtreecommitdiff
path: root/date.c
diff options
context:
space:
mode:
Diffstat (limited to 'date.c')
-rw-r--r--date.c189
1 files changed, 149 insertions, 40 deletions
diff --git a/date.c b/date.c
index 9bc15df6f9..b0d9a8421d 100644
--- a/date.c
+++ b/date.c
@@ -64,35 +64,29 @@ static time_t gm_time_t(timestamp_t time, int tz)
* thing, which means that tz -0100 is passed in as the integer -100,
* even though it means "sixty minutes off"
*/
-static struct tm *time_to_tm(timestamp_t time, int tz)
+static struct tm *time_to_tm(timestamp_t time, int tz, struct tm *tm)
{
time_t t = gm_time_t(time, tz);
- return gmtime(&t);
+ return gmtime_r(&t, tm);
}
-static struct tm *time_to_tm_local(timestamp_t time)
+static struct tm *time_to_tm_local(timestamp_t time, struct tm *tm)
{
time_t t = time;
- return localtime(&t);
+ return localtime_r(&t, tm);
}
/*
- * 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,16 +101,44 @@ static int local_tzoffset(timestamp_t time)
return offset * eastwest;
}
-void show_date_relative(timestamp_t time, int tz,
- const struct timeval *now,
- struct strbuf *timebuf)
+/*
+ * 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, struct strbuf *timebuf)
{
+ struct timeval now;
timestamp_t diff;
- if (now->tv_sec < time) {
+
+ get_time(&now);
+ if (now.tv_sec < time) {
strbuf_addstr(timebuf, _("in the future"));
return;
}
- diff = now->tv_sec - time;
+ diff = now.tv_sec - time;
if (diff < 90) {
strbuf_addf(timebuf,
Q_("%"PRItime" second ago", "%"PRItime" seconds ago", diff), diff);
@@ -191,9 +213,79 @@ 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) {
+ show_date_relative(time, 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 tmbuf = { 0 };
+ 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);
@@ -212,20 +313,17 @@ const char *show_date(timestamp_t time, int tz, const struct date_mode *mode)
}
if (mode->type == DATE_RELATIVE) {
- struct timeval now;
-
strbuf_reset(&timebuf);
- gettimeofday(&now, NULL);
- show_date_relative(time, tz, &now, &timebuf);
+ show_date_relative(time, &timebuf);
return timebuf.buf;
}
if (mode->local)
- tm = time_to_tm_local(time);
+ tm = time_to_tm_local(time, &tmbuf);
else
- tm = time_to_tm(time, tz);
+ tm = time_to_tm(time, tz, &tmbuf);
if (!tm) {
- tm = time_to_tm(0, 0);
+ tm = time_to_tm(0, 0, &tmbuf);
tz = 0;
}
@@ -258,14 +356,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,12 +910,18 @@ 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))
return DATE_UNIX;
if (skip_prefix(format, "format", end))
return DATE_STRFTIME;
+ /*
+ * Please update $__git_log_date_formats in
+ * git-completion.bash when you add new formats.
+ */
die("unknown date format %s", format);
}
@@ -833,6 +930,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";
@@ -855,10 +960,11 @@ void datestamp(struct strbuf *out)
{
time_t now;
int offset;
+ struct tm tm = { 0 };
time(&now);
- offset = tm_to_time_t(localtime(&now)) - now;
+ offset = tm_to_time_t(localtime_r(&now, &tm)) - now;
offset /= 60;
date_string(now, offset, out);
@@ -1180,15 +1286,18 @@ static timestamp_t approxidate_str(const char *date,
return (timestamp_t)update_tm(&tm, &now, 0);
}
-timestamp_t approxidate_relative(const char *date, const struct timeval *tv)
+timestamp_t approxidate_relative(const char *date)
{
+ struct timeval tv;
timestamp_t timestamp;
int offset;
int errors = 0;
if (!parse_date_basic(date, &timestamp, &offset))
return timestamp;
- return approxidate_str(date, tv, &errors);
+
+ get_time(&tv);
+ return approxidate_str(date, (const struct timeval *) &tv, &errors);
}
timestamp_t approxidate_careful(const char *date, int *error_ret)
@@ -1205,7 +1314,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);
}