Fix parsing of @{-1}@{1}
[gitweb.git] / date.c
diff --git a/date.c b/date.c
index 316841e8ad3705559fddb0ca4aff969fdf91b011..950b88fdcf74f550a582684f1702ffb58c62c7f9 100644 (file)
--- a/date.c
+++ b/date.c
@@ -6,7 +6,10 @@
 
 #include "cache.h"
 
-static time_t my_mktime(struct tm *tm)
+/*
+ * This is like mktime, but without normalization of tm_wday and tm_yday.
+ */
+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
@@ -67,7 +70,7 @@ static int local_tzoffset(unsigned long time)
 
        t = time;
        localtime_r(&t, &tm);
-       t_local = my_mktime(&tm);
+       t_local = tm_to_time_t(&tm);
 
        if (t_local < t) {
                eastwest = -1;
@@ -137,6 +140,18 @@ const char *show_date(unsigned long time, int tz, enum date_mode mode)
        if (mode == DATE_SHORT)
                sprintf(timebuf, "%04d-%02d-%02d", tm->tm_year + 1900,
                                tm->tm_mon + 1, tm->tm_mday);
+       else if (mode == DATE_ISO8601)
+               sprintf(timebuf, "%04d-%02d-%02d %02d:%02d:%02d %+05d",
+                               tm->tm_year + 1900,
+                               tm->tm_mon + 1,
+                               tm->tm_mday,
+                               tm->tm_hour, tm->tm_min, tm->tm_sec,
+                               tz);
+       else if (mode == DATE_RFC2822)
+               sprintf(timebuf, "%.3s, %d %.3s %d %02d:%02d:%02d %+05d",
+                       weekday_names[tm->tm_wday], tm->tm_mday,
+                       month_names[tm->tm_mon], tm->tm_year + 1900,
+                       tm->tm_hour, tm->tm_min, tm->tm_sec, tz);
        else
                sprintf(timebuf, "%.3s %.3s %d %02d:%02d:%02d %d%c%+05d",
                                weekday_names[tm->tm_wday],
@@ -149,21 +164,6 @@ const char *show_date(unsigned long time, int tz, enum date_mode mode)
        return timebuf;
 }
 
-const char *show_rfc2822_date(unsigned long time, int tz)
-{
-       struct tm *tm;
-       static char timebuf[200];
-
-       tm = time_to_tm(time, tz);
-       if (!tm)
-               return NULL;
-       sprintf(timebuf, "%.3s, %d %.3s %d %02d:%02d:%02d %+05d",
-               weekday_names[tm->tm_wday], tm->tm_mday,
-               month_names[tm->tm_mon], tm->tm_year + 1900,
-               tm->tm_hour, tm->tm_min, tm->tm_sec, tz);
-       return timebuf;
-}
-
 /*
  * Check these. And note how it doesn't do the summer-time conversion.
  *
@@ -216,9 +216,9 @@ static const struct {
        { "EAST", +10, 0, },    /* Eastern Australian Standard */
        { "EADT", +10, 1, },    /* Eastern Australian Daylight */
        { "GST",  +10, 0, },    /* Guam Standard, USSR Zone 9 */
-       { "NZT",  +11, 0, },    /* New Zealand */
-       { "NZST", +11, 0, },    /* New Zealand Standard */
-       { "NZDT", +11, 1, },    /* New Zealand Daylight */
+       { "NZT",  +12, 0, },    /* New Zealand */
+       { "NZST", +12, 0, },    /* New Zealand Standard */
+       { "NZDT", +12, 1, },    /* New Zealand Daylight */
        { "IDLE", +12, 0, },    /* International Date Line East */
 };
 
@@ -325,7 +325,7 @@ static int is_date(int year, int month, int day, struct tm *now_tm, time_t now,
                if (!now_tm)
                        return 1;
 
-               specified = my_mktime(r);
+               specified = tm_to_time_t(r);
 
                /* Be it commit time or author time, it does not make
                 * sense to specify timestamp way into the future.  Make
@@ -402,6 +402,15 @@ static int match_multi_number(unsigned long num, char c, const char *date, char
        return end - date;
 }
 
+/* Have we filled in any part of the time/date yet? */
+static inline int nodate(struct tm *tm)
+{
+       return tm->tm_year < 0 &&
+               tm->tm_mon < 0 &&
+               tm->tm_mday < 0 &&
+               !(tm->tm_hour | tm->tm_min | tm->tm_sec);
+}
+
 /*
  * We've seen a digit. Time? Year? Date?
  */
@@ -418,7 +427,7 @@ static int match_digit(const char *date, struct tm *tm, int *offset, int *tm_gmt
         * more than 8 digits. This is because we don't want to rule out
         * numbers like 20070606 as a YYYYMMDD date.
         */
-       if (num >= 100000000) {
+       if (num >= 100000000 && nodate(tm)) {
                time_t time = num;
                if (gmtime_r(&time, tm)) {
                        *tm_gmt = 1;
@@ -462,6 +471,13 @@ static int match_digit(const char *date, struct tm *tm, int *offset, int *tm_gmt
                return n;
        }
 
+       /*
+        * Ignore lots of numerals. We took care of 4-digit years above.
+        * Days or months must be one or two digits.
+        */
+       if (n > 2)
+               return n;
+
        /*
         * NOTE! We will give precedence to day-of-month over month or
         * year numbers in the 1-12 range. So 05 is always "mday 5",
@@ -488,10 +504,6 @@ static int match_digit(const char *date, struct tm *tm, int *offset, int *tm_gmt
 
        if (num > 0 && num < 32) {
                tm->tm_mday = num;
-       } else if (num > 1900) {
-               tm->tm_year = num - 1900;
-       } else if (num > 70) {
-               tm->tm_year = num;
        } else if (num > 0 && num < 13) {
                tm->tm_mon = num-1;
        }
@@ -575,7 +587,7 @@ int parse_date(const char *date, char *result, int maxlen)
        }
 
        /* mktime uses local timezone */
-       then = my_mktime(&tm);
+       then = tm_to_time_t(&tm);
        if (offset == -1)
                offset = (then - mktime(&tm)) / 60;
 
@@ -587,6 +599,26 @@ int parse_date(const char *date, char *result, int maxlen)
        return date_string(then, offset, result, maxlen);
 }
 
+enum date_mode parse_date_format(const char *format)
+{
+       if (!strcmp(format, "relative"))
+               return DATE_RELATIVE;
+       else if (!strcmp(format, "iso8601") ||
+                !strcmp(format, "iso"))
+               return DATE_ISO8601;
+       else if (!strcmp(format, "rfc2822") ||
+                !strcmp(format, "rfc"))
+               return DATE_RFC2822;
+       else if (!strcmp(format, "short"))
+               return DATE_SHORT;
+       else if (!strcmp(format, "local"))
+               return DATE_LOCAL;
+       else if (!strcmp(format, "default"))
+               return DATE_NORMAL;
+       else
+               die("unknown date format %s", format);
+}
+
 void datestamp(char *buf, int bufsize)
 {
        time_t now;
@@ -594,7 +626,7 @@ void datestamp(char *buf, int bufsize)
 
        time(&now);
 
-       offset = my_mktime(localtime(&now)) - now;
+       offset = tm_to_time_t(localtime(&now)) - now;
        offset /= 60;
 
        date_string(now, offset, buf, bufsize);
@@ -663,6 +695,12 @@ static void date_am(struct tm *tm, int *num)
        tm->tm_hour = (hour % 12);
 }
 
+static void date_never(struct tm *tm, int *num)
+{
+       time_t n = 0;
+       localtime_r(&n, tm);
+}
+
 static const struct special {
        const char *name;
        void (*fn)(struct tm *, int *);
@@ -673,6 +711,7 @@ static const struct special {
        { "tea", date_tea },
        { "PM", date_pm },
        { "AM", date_am },
+       { "never", date_never },
        { NULL }
 };
 
@@ -796,7 +835,9 @@ static const char *approxidate_digit(const char *date, struct tm *tm, int *num)
                }
        }
 
-       *num = number;
+       /* Accept zero-padding only for small numbers ("Dec 02", never "Dec 0002") */
+       if (date[0] != '0' || end - date <= 2)
+               *num = number;
        return end;
 }