fast-import: prevent producing bad delta
[gitweb.git] / date.c
diff --git a/date.c b/date.c
index 5d05ef61cfb140f004702a5ed614afa755c50670..896fbb48060c673b8dc2f2aa6ea3de3c1042fbd2 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
@@ -129,8 +129,9 @@ const char *show_date_relative(unsigned long time, int tz,
        }
        /* Give years and months for 5 years or so */
        if (diff < 1825) {
-               unsigned long years = diff / 365;
-               unsigned long months = (diff % 365 + 15) / 30;
+               unsigned long totalmonths = (diff * 12 * 2 + 365) / (365 * 2);
+               unsigned long years = totalmonths / 12;
+               unsigned long months = totalmonths % 12;
                int n;
                n = snprintf(timebuf, timebuf_size, "%lu year%s",
                                years, (years > 1 ? "s" : ""));
@@ -229,6 +230,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 */
@@ -305,7 +307,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 */
@@ -585,11 +587,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_basic(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;
@@ -599,7 +607,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 (;;) {
@@ -611,11 +619,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 */
@@ -626,16 +634,25 @@ 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 0; /* success */
+}
+
+int parse_date(const char *date, char *result, int maxlen)
+{
+       unsigned long timestamp;
+       int offset;
+       if (parse_date_basic(date, &timestamp, &offset))
+               return -1;
+       return date_string(timestamp, offset, result, maxlen);
 }
 
 enum date_mode parse_date_format(const char *format)
@@ -696,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);
@@ -770,6 +792,7 @@ static const struct special {
        { "PM", date_pm },
        { "AM", date_am },
        { "never", date_never },
+       { "now", date_now },
        { NULL }
 };
 
@@ -790,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;
@@ -804,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;
                }
        }
@@ -812,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;
                }
        }
@@ -821,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;
        }
 
@@ -835,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++;
@@ -852,6 +881,7 @@ 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;
                }
        }
@@ -866,6 +896,7 @@ static const char *approxidate_alpha(const char *date, struct tm *tm, struct tm
                        tm->tm_year--;
                }
                tm->tm_mon = n;
+               *touched = 1;
                return end;
        }
 
@@ -873,6 +904,7 @@ static const char *approxidate_alpha(const char *date, struct tm *tm, struct tm
                update_tm(tm, now, 0); /* fill in date fields if needed */
                tm->tm_year -= *num;
                *num = 0;
+               *touched = 1;
                return end;
        }
 
@@ -929,9 +961,12 @@ static void pending_number(struct tm *tm, int *num)
        }
 }
 
-static unsigned long approxidate_str(const char *date, const struct timeval *tv)
+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;
        time_t time_sec;
 
@@ -951,33 +986,43 @@ static unsigned long approxidate_str(const char *date, const struct timeval *tv)
                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)
 {
-       char buffer[50];
-
-       if (parse_date(date, buffer, sizeof(buffer)) > 0)
-               return strtoul(buffer, NULL, 0);
+       unsigned long timestamp;
+       int offset;
+       int errors = 0;
 
-       return approxidate_str(date, tv);
+       if (!parse_date_basic(date, &timestamp, &offset))
+               return timestamp;
+       return approxidate_str(date, tv, &errors);
 }
 
-unsigned long approxidate(const char *date)
+unsigned long approxidate_careful(const char *date, int *error_ret)
 {
        struct timeval tv;
-       char buffer[50];
+       unsigned long timestamp;
+       int offset;
+       int dummy = 0;
+       if (!error_ret)
+               error_ret = &dummy;
 
-       if (parse_date(date, buffer, sizeof(buffer)) > 0)
-               return strtoul(buffer, NULL, 0);
+       if (!parse_date_basic(date, &timestamp, &offset)) {
+               *error_ret = 0;
+               return timestamp;
+       }
 
        gettimeofday(&tv, NULL);
-       return approxidate_str(date, &tv);
+       return approxidate_str(date, &tv, error_ret);
 }