apply: split quoted filename handling into new function
[gitweb.git] / date.c
diff --git a/date.c b/date.c
index 1de184587ba33e8c677ec8040c4c4e177ef41c55..3c981f7eb5ebaed1ba621f3d4cd960f14145b97a 100644 (file)
--- a/date.c
+++ b/date.c
@@ -9,7 +9,7 @@
 /*
  * This is like mktime, but without normalization of tm_wday and tm_yday.
  */
-time_t tm_to_time_t(const struct tm *tm)
+static time_t tm_to_time_t(const struct tm *tm)
 {
        static const int mdays[] = {
            0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334
@@ -86,6 +86,67 @@ static int local_tzoffset(unsigned long time)
        return offset * eastwest;
 }
 
+const char *show_date_relative(unsigned long time, int tz,
+                              const struct timeval *now,
+                              char *timebuf,
+                              size_t timebuf_size)
+{
+       unsigned long diff;
+       if (now->tv_sec < time)
+               return "in the future";
+       diff = now->tv_sec - time;
+       if (diff < 90) {
+               snprintf(timebuf, timebuf_size, "%lu seconds ago", diff);
+               return timebuf;
+       }
+       /* Turn it into minutes */
+       diff = (diff + 30) / 60;
+       if (diff < 90) {
+               snprintf(timebuf, timebuf_size, "%lu minutes ago", diff);
+               return timebuf;
+       }
+       /* Turn it into hours */
+       diff = (diff + 30) / 60;
+       if (diff < 36) {
+               snprintf(timebuf, timebuf_size, "%lu hours ago", diff);
+               return timebuf;
+       }
+       /* We deal with number of days from here on */
+       diff = (diff + 12) / 24;
+       if (diff < 14) {
+               snprintf(timebuf, timebuf_size, "%lu days ago", diff);
+               return timebuf;
+       }
+       /* Say weeks for the past 10 weeks or so */
+       if (diff < 70) {
+               snprintf(timebuf, timebuf_size, "%lu weeks ago", (diff + 3) / 7);
+               return timebuf;
+       }
+       /* Say months for the past 12 months or so */
+       if (diff < 365) {
+               snprintf(timebuf, timebuf_size, "%lu months ago", (diff + 15) / 30);
+               return timebuf;
+       }
+       /* Give years and months for 5 years or so */
+       if (diff < 1825) {
+               unsigned long years = diff / 365;
+               unsigned long months = (diff % 365 + 15) / 30;
+               int n;
+               n = snprintf(timebuf, timebuf_size, "%lu year%s",
+                               years, (years > 1 ? "s" : ""));
+               if (months)
+                       snprintf(timebuf + n, timebuf_size - n,
+                                       ", %lu month%s ago",
+                                       months, (months > 1 ? "s" : ""));
+               else
+                       snprintf(timebuf + n, timebuf_size - n, " ago");
+               return timebuf;
+       }
+       /* Otherwise, just years. Centuries is probably overkill. */
+       snprintf(timebuf, timebuf_size, "%lu years ago", (diff + 183) / 365);
+       return timebuf;
+}
+
 const char *show_date(unsigned long time, int tz, enum date_mode mode)
 {
        struct tm *tm;
@@ -97,63 +158,10 @@ const char *show_date(unsigned long time, int tz, enum date_mode mode)
        }
 
        if (mode == DATE_RELATIVE) {
-               unsigned long diff;
                struct timeval now;
                gettimeofday(&now, NULL);
-               if (now.tv_sec < time)
-                       return "in the future";
-               diff = now.tv_sec - time;
-               if (diff < 90) {
-                       snprintf(timebuf, sizeof(timebuf), "%lu seconds ago", diff);
-                       return timebuf;
-               }
-               /* Turn it into minutes */
-               diff = (diff + 30) / 60;
-               if (diff < 90) {
-                       snprintf(timebuf, sizeof(timebuf), "%lu minutes ago", diff);
-                       return timebuf;
-               }
-               /* Turn it into hours */
-               diff = (diff + 30) / 60;
-               if (diff < 36) {
-                       snprintf(timebuf, sizeof(timebuf), "%lu hours ago", diff);
-                       return timebuf;
-               }
-               /* We deal with number of days from here on */
-               diff = (diff + 12) / 24;
-               if (diff < 14) {
-                       snprintf(timebuf, sizeof(timebuf), "%lu days ago", diff);
-                       return timebuf;
-               }
-               /* Say weeks for the past 10 weeks or so */
-               if (diff < 70) {
-                       snprintf(timebuf, sizeof(timebuf), "%lu weeks ago", (diff + 3) / 7);
-                       return timebuf;
-               }
-               /* Say months for the past 12 months or so */
-               if (diff < 360) {
-                       snprintf(timebuf, sizeof(timebuf), "%lu months ago", (diff + 15) / 30);
-                       return timebuf;
-               }
-               /* Give years and months for 5 years or so */
-               if (diff < 1825) {
-                       unsigned long years = (diff + 183) / 365;
-                       unsigned long months = (diff % 365 + 15) / 30;
-                       int n;
-                       n = snprintf(timebuf, sizeof(timebuf), "%lu year%s",
-                                       years, (years > 1 ? "s" : ""));
-                       if (months)
-                               snprintf(timebuf + n, sizeof(timebuf) - n,
-                                       ", %lu month%s ago",
-                                       months, (months > 1 ? "s" : ""));
-                       else
-                               snprintf(timebuf + n, sizeof(timebuf) - n,
-                                       " ago");
-                       return timebuf;
-               }
-               /* Otherwise, just years. Centuries is probably overkill. */
-               snprintf(timebuf, sizeof(timebuf), "%lu years ago", (diff + 183) / 365);
-               return timebuf;
+               return show_date_relative(time, tz, &now,
+                                         timebuf, sizeof(timebuf));
        }
 
        if (mode == DATE_LOCAL)
@@ -221,6 +229,7 @@ static const struct {
 
        { "GMT",    0, 0, },    /* Greenwich Mean */
        { "UTC",    0, 0, },    /* Universal (Coordinated) */
+       { "Z",      0, 0, },    /* Zulu, alias for UTC */
 
        { "WET",    0, 0, },    /* Western European */
        { "BST",    0, 1, },    /* British Summer */
@@ -297,7 +306,7 @@ static int match_alpha(const char *date, struct tm *tm, int *offset)
 
        for (i = 0; i < ARRAY_SIZE(timezone_names); i++) {
                int match = match_string(date, timezone_names[i].name);
-               if (match >= 3) {
+               if (match >= 3 || match == strlen(timezone_names[i].name)) {
                        int off = timezone_names[i].offset;
 
                        /* This is bogus, but we like summer */
@@ -577,11 +586,17 @@ static int date_string(unsigned long date, int offset, char *buf, int len)
 
 /* Gr. strptime is crap for this; it doesn't have a way to require RFC2822
    (i.e. English) day/month names, and it doesn't work correctly with %z. */
-int parse_date(const char *date, char *result, int maxlen)
+int parse_date_toffset(const char *date, unsigned long *timestamp, int *offset)
 {
        struct tm tm;
-       int offset, tm_gmt;
-       time_t then;
+       int tm_gmt;
+       unsigned long dummy_timestamp;
+       int dummy_offset;
+
+       if (!timestamp)
+               timestamp = &dummy_timestamp;
+       if (!offset)
+               offset = &dummy_offset;
 
        memset(&tm, 0, sizeof(tm));
        tm.tm_year = -1;
@@ -591,7 +606,7 @@ int parse_date(const char *date, char *result, int maxlen)
        tm.tm_hour = -1;
        tm.tm_min = -1;
        tm.tm_sec = -1;
-       offset = -1;
+       *offset = -1;
        tm_gmt = 0;
 
        for (;;) {
@@ -603,11 +618,11 @@ int parse_date(const char *date, char *result, int maxlen)
                        break;
 
                if (isalpha(c))
-                       match = match_alpha(date, &tm, &offset);
+                       match = match_alpha(date, &tm, offset);
                else if (isdigit(c))
-                       match = match_digit(date, &tm, &offset, &tm_gmt);
+                       match = match_digit(date, &tm, offset, &tm_gmt);
                else if ((c == '-' || c == '+') && isdigit(date[1]))
-                       match = match_tz(date, &offset);
+                       match = match_tz(date, offset);
 
                if (!match) {
                        /* BAD CRAP */
@@ -618,16 +633,26 @@ int parse_date(const char *date, char *result, int maxlen)
        }
 
        /* mktime uses local timezone */
-       then = tm_to_time_t(&tm);
-       if (offset == -1)
-               offset = (then - mktime(&tm)) / 60;
+       *timestamp = tm_to_time_t(&tm);
+       if (*offset == -1)
+               *offset = ((time_t)*timestamp - mktime(&tm)) / 60;
 
-       if (then == -1)
+       if (*timestamp == -1)
                return -1;
 
        if (!tm_gmt)
-               then -= offset * 60;
-       return date_string(then, offset, result, maxlen);
+               *timestamp -= *offset * 60;
+       return 1; /* success */
+}
+
+int parse_date(const char *date, char *result, int maxlen)
+{
+       unsigned long timestamp;
+       int offset;
+       if (parse_date_toffset(date, &timestamp, &offset) > 0)
+               return date_string(timestamp, offset, result, maxlen);
+       else
+               return -1;
 }
 
 enum date_mode parse_date_format(const char *format)
@@ -688,6 +713,11 @@ static unsigned long update_tm(struct tm *tm, struct tm *now, unsigned long sec)
        return n;
 }
 
+static void date_now(struct tm *tm, struct tm *now, int *num)
+{
+       update_tm(tm, now, 0);
+}
+
 static void date_yesterday(struct tm *tm, struct tm *now, int *num)
 {
        update_tm(tm, now, 24*60*60);
@@ -762,6 +792,7 @@ static const struct special {
        { "PM", date_pm },
        { "AM", date_am },
        { "never", date_never },
+       { "now", date_now },
        { NULL }
 };
 
@@ -782,7 +813,7 @@ static const struct typelen {
        { NULL }
 };
 
-static const char *approxidate_alpha(const char *date, struct tm *tm, struct tm *now, int *num)
+static const char *approxidate_alpha(const char *date, struct tm *tm, struct tm *now, int *num, int *touched)
 {
        const struct typelen *tl;
        const struct special *s;
@@ -796,6 +827,7 @@ static const char *approxidate_alpha(const char *date, struct tm *tm, struct tm
                int match = match_string(date, month_names[i]);
                if (match >= 3) {
                        tm->tm_mon = i;
+                       *touched = 1;
                        return end;
                }
        }
@@ -804,6 +836,7 @@ static const char *approxidate_alpha(const char *date, struct tm *tm, struct tm
                int len = strlen(s->name);
                if (match_string(date, s->name) == len) {
                        s->fn(tm, now, num);
+                       *touched = 1;
                        return end;
                }
        }
@@ -813,11 +846,14 @@ static const char *approxidate_alpha(const char *date, struct tm *tm, struct tm
                        int len = strlen(number_name[i]);
                        if (match_string(date, number_name[i]) == len) {
                                *num = i;
+                               *touched = 1;
                                return end;
                        }
                }
-               if (match_string(date, "last") == 4)
+               if (match_string(date, "last") == 4) {
                        *num = 1;
+                       *touched = 1;
+               }
                return end;
        }
 
@@ -827,6 +863,7 @@ static const char *approxidate_alpha(const char *date, struct tm *tm, struct tm
                if (match_string(date, tl->type) >= len-1) {
                        update_tm(tm, now, tl->length * *num);
                        *num = 0;
+                       *touched = 1;
                        return end;
                }
                tl++;
@@ -844,24 +881,30 @@ static const char *approxidate_alpha(const char *date, struct tm *tm, struct tm
                        diff += 7*n;
 
                        update_tm(tm, now, diff * 24 * 60 * 60);
+                       *touched = 1;
                        return end;
                }
        }
 
        if (match_string(date, "months") >= 5) {
-               int n = tm->tm_mon - *num;
+               int n;
+               update_tm(tm, now, 0); /* fill in date fields if needed */
+               n = tm->tm_mon - *num;
                *num = 0;
                while (n < 0) {
                        n += 12;
                        tm->tm_year--;
                }
                tm->tm_mon = n;
+               *touched = 1;
                return end;
        }
 
        if (match_string(date, "years") >= 4) {
+               update_tm(tm, now, 0); /* fill in date fields if needed */
                tm->tm_year -= *num;
                *num = 0;
+               *touched = 1;
                return end;
        }
 
@@ -918,19 +961,16 @@ static void pending_number(struct tm *tm, int *num)
        }
 }
 
-unsigned long approxidate(const char *date)
+static unsigned long approxidate_str(const char *date,
+                                    const struct timeval *tv,
+                                    int *error_ret)
 {
        int number = 0;
+       int touched = 0;
        struct tm tm, now;
-       struct timeval tv;
        time_t time_sec;
-       char buffer[50];
 
-       if (parse_date(date, buffer, sizeof(buffer)) > 0)
-               return strtoul(buffer, NULL, 10);
-
-       gettimeofday(&tv, NULL);
-       time_sec = tv.tv_sec;
+       time_sec = tv->tv_sec;
        localtime_r(&time_sec, &tm);
        now = tm;
 
@@ -946,11 +986,44 @@ unsigned long approxidate(const char *date)
                if (isdigit(c)) {
                        pending_number(&tm, &number);
                        date = approxidate_digit(date-1, &tm, &number);
+                       touched = 1;
                        continue;
                }
                if (isalpha(c))
-                       date = approxidate_alpha(date-1, &tm, &now, &number);
+                       date = approxidate_alpha(date-1, &tm, &now, &number, &touched);
        }
        pending_number(&tm, &number);
+       if (!touched)
+               *error_ret = 1;
        return update_tm(&tm, &now, 0);
 }
+
+unsigned long approxidate_relative(const char *date, const struct timeval *tv)
+{
+       unsigned long timestamp;
+       int offset;
+       int errors = 0;
+
+       if (parse_date_toffset(date, &timestamp, &offset) > 0)
+               return timestamp;
+
+       return approxidate_str(date, tv, &errors);
+}
+
+unsigned long approxidate_careful(const char *date, int *error_ret)
+{
+       struct timeval tv;
+       unsigned long timestamp;
+       int offset;
+       int dummy = 0;
+       if (!error_ret)
+               error_ret = &dummy;
+
+       if (parse_date_toffset(date, &timestamp, &offset) > 0) {
+               *error_ret = 0;
+               return timestamp;
+       }
+
+       gettimeofday(&tv, NULL);
+       return approxidate_str(date, &tv, error_ret);
+}