Bisect: refactor "bisect_{bad,good,skip}" into "bisect_state".
[gitweb.git] / date.c
diff --git a/date.c b/date.c
index 66be23ab21ef855ecbadeceae1dea2972efd4c2f..8f7050027053a4e2390097e341327b117404c26a 100644 (file)
--- a/date.c
+++ b/date.c
@@ -4,9 +4,6 @@
  * Copyright (C) Linus Torvalds, 2005
  */
 
-#include <time.h>
-#include <sys/time.h>
-
 #include "cache.h"
 
 static time_t my_mktime(struct tm *tm)
@@ -37,6 +34,16 @@ static const char *weekday_names[] = {
        "Sundays", "Mondays", "Tuesdays", "Wednesdays", "Thursdays", "Fridays", "Saturdays"
 };
 
+static time_t gm_time_t(unsigned long time, int tz)
+{
+       int minutes;
+
+       minutes = tz < 0 ? -tz : tz;
+       minutes = (minutes / 100)*60 + (minutes % 100);
+       minutes = tz < 0 ? -minutes : minutes;
+       return time + minutes * 60;
+}
+
 /*
  * The "tz" thing is passed in as this strange "decimal parse of tz"
  * thing, which means that tz -0100 is passed in as the integer -100,
@@ -44,45 +51,113 @@ static const char *weekday_names[] = {
  */
 static struct tm *time_to_tm(unsigned long time, int tz)
 {
-       time_t t;
-       int minutes;
-
-       minutes = tz < 0 ? -tz : tz;
-       minutes = (minutes / 100)*60 + (minutes % 100);
-       minutes = tz < 0 ? -minutes : minutes;
-       t = time + minutes * 60;
+       time_t t = gm_time_t(time, tz);
        return gmtime(&t);
 }
 
-const char *show_date(unsigned long time, int tz)
+/*
+ * What value of "tz" was in effect back then at "time" in the
+ * local timezone?
+ */
+static int local_tzoffset(unsigned long time)
 {
-       struct tm *tm;
-       static char timebuf[200];
-
-       tm = time_to_tm(time, tz);
-       if (!tm)
-               return NULL;
-       sprintf(timebuf, "%.3s %.3s %d %02d:%02d:%02d %d %+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, tz);
-       return timebuf;
+       time_t t, t_local;
+       struct tm tm;
+       int offset, eastwest;
+
+       t = time;
+       localtime_r(&t, &tm);
+       t_local = my_mktime(&tm);
+
+       if (t_local < t) {
+               eastwest = -1;
+               offset = t - t_local;
+       } else {
+               eastwest = 1;
+               offset = t_local - t;
+       }
+       offset /= 60; /* in minutes */
+       offset = (offset % 60) + ((offset / 60) * 100);
+       return offset * eastwest;
 }
 
-const char *show_rfc2822_date(unsigned long time, int tz)
+const char *show_date(unsigned long time, int tz, enum date_mode mode)
 {
        struct tm *tm;
        static char timebuf[200];
 
+       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;
+               }
+               /* Else fall back on absolute format.. */
+       }
+
+       if (mode == DATE_LOCAL)
+               tz = local_tzoffset(time);
+
        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);
+       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],
+                               month_names[tm->tm_mon],
+                               tm->tm_mday,
+                               tm->tm_hour, tm->tm_min, tm->tm_sec,
+                               tm->tm_year + 1900,
+                               (mode == DATE_LOCAL) ? 0 : ' ',
+                               tz);
        return timebuf;
 }
 
@@ -209,8 +284,12 @@ static int match_alpha(const char *date, struct tm *tm, int *offset)
        }
 
        if (match_string(date, "PM") == 2) {
-               if (tm->tm_hour > 0 && tm->tm_hour < 12)
-                       tm->tm_hour += 12;
+               tm->tm_hour = (tm->tm_hour % 12) + 12;
+               return 2;
+       }
+
+       if (match_string(date, "AM") == 2) {
+               tm->tm_hour = (tm->tm_hour % 12) + 0;
                return 2;
        }
 
@@ -321,7 +400,7 @@ static int match_multi_number(unsigned long num, char c, const char *date, char
 }
 
 /*
- * We've seen a digit. Time? Year? Date? 
+ * We've seen a digit. Time? Year? Date?
  */
 static int match_digit(const char *date, struct tm *tm, int *offset, int *tm_gmt)
 {
@@ -332,9 +411,11 @@ static int match_digit(const char *date, struct tm *tm, int *offset, int *tm_gmt
        num = strtoul(date, &end, 10);
 
        /*
-        * Seconds since 1970? We trigger on that for anything after Jan 1, 2000
+        * Seconds since 1970? We trigger on that for any numbers with
+        * more than 8 digits. This is because we don't want to rule out
+        * numbers like 20070606 as a YYYYMMDD date.
         */
-       if (num > 946684800) {
+       if (num >= 100000000) {
                time_t time = num;
                if (gmtime_r(&time, tm)) {
                        *tm_gmt = 1;
@@ -411,7 +492,7 @@ static int match_digit(const char *date, struct tm *tm, int *offset, int *tm_gmt
        } else if (num > 0 && num < 13) {
                tm->tm_mon = num-1;
        }
-               
+
        return n;
 }
 
@@ -485,13 +566,13 @@ int parse_date(const char *date, char *result, int maxlen)
                if (!match) {
                        /* BAD CRAP */
                        match = 1;
-               }       
+               }
 
                date += match;
        }
 
        /* mktime uses local timezone */
-       then = my_mktime(&tm); 
+       then = my_mktime(&tm);
        if (offset == -1)
                offset = (then - mktime(&tm)) / 60;
 
@@ -503,6 +584,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;
@@ -551,6 +652,42 @@ static void date_tea(struct tm *tm, int *num)
        date_time(tm, 17);
 }
 
+static void date_pm(struct tm *tm, int *num)
+{
+       int hour, n = *num;
+       *num = 0;
+
+       hour = tm->tm_hour;
+       if (n) {
+               hour = n;
+               tm->tm_min = 0;
+               tm->tm_sec = 0;
+       }
+       tm->tm_hour = (hour % 12) + 12;
+}
+
+static void date_am(struct tm *tm, int *num)
+{
+       int hour, n = *num;
+       *num = 0;
+
+       hour = tm->tm_hour;
+       if (n) {
+               hour = n;
+               tm->tm_min = 0;
+               tm->tm_sec = 0;
+       }
+       tm->tm_hour = (hour % 12);
+}
+
+static void date_never(struct tm *tm, int *num)
+{
+       tm->tm_mon = tm->tm_wday = tm->tm_yday
+               = tm->tm_hour = tm->tm_min = tm->tm_sec = 0;
+       tm->tm_year = 70;
+       tm->tm_mday = 1;
+}
+
 static const struct special {
        const char *name;
        void (*fn)(struct tm *, int *);
@@ -559,6 +696,9 @@ static const struct special {
        { "noon", date_noon },
        { "midnight", date_midnight },
        { "tea", date_tea },
+       { "PM", date_pm },
+       { "AM", date_am },
+       { "never", date_never },
        { NULL }
 };
 
@@ -577,17 +717,17 @@ static const struct typelen {
        { "days", 24*60*60 },
        { "weeks", 7*24*60*60 },
        { NULL }
-};     
+};
 
 static const char *approxidate_alpha(const char *date, struct tm *tm, int *num)
 {
        const struct typelen *tl;
        const struct special *s;
        const char *end = date;
-       int n = 1, i;
+       int i;
 
-       while (isalpha(*++end))
-               n++;
+       while (isalpha(*++end));
+               ;
 
        for (i = 0; i < 12; i++) {
                int match = match_string(date, month_names[i]);
@@ -665,6 +805,27 @@ static const char *approxidate_alpha(const char *date, struct tm *tm, int *num)
        return end;
 }
 
+static const char *approxidate_digit(const char *date, struct tm *tm, int *num)
+{
+       char *end;
+       unsigned long number = strtoul(date, &end, 10);
+
+       switch (*end) {
+       case ':':
+       case '.':
+       case '/':
+       case '-':
+               if (isdigit(end[1])) {
+                       int match = match_multi_number(number, *end, date, end, tm);
+                       if (match)
+                               return date + match;
+               }
+       }
+
+       *num = number;
+       return end;
+}
+
 unsigned long approxidate(const char *date)
 {
        int number = 0;
@@ -684,9 +845,7 @@ unsigned long approxidate(const char *date)
                        break;
                date++;
                if (isdigit(c)) {
-                       char *end;
-                       number = strtoul(date-1, &end, 10);
-                       date = end;
+                       date = approxidate_digit(date-1, &tm, &number);
                        continue;
                }
                if (isalpha(c))