Code

grep doc: add --break / --heading / -W to synopsis
[git.git] / date.c
diff --git a/date.c b/date.c
index 896fbb48060c673b8dc2f2aa6ea3de3c1042fbd2..a5055ca09dc1fafce2b9434c4fda02ad4f8e117f 100644 (file)
--- a/date.c
+++ b/date.c
@@ -552,23 +552,35 @@ static int match_digit(const char *date, struct tm *tm, int *offset, int *tm_gmt
 static int match_tz(const char *date, int *offp)
 {
        char *end;
-       int offset = strtoul(date+1, &end, 10);
-       int min, hour;
-       int n = end - date - 1;
+       int hour = strtoul(date + 1, &end, 10);
+       int n = end - (date + 1);
+       int min = 0;
 
-       min = offset % 100;
-       hour = offset / 100;
+       if (n == 4) {
+               /* hhmm */
+               min = hour % 100;
+               hour = hour / 100;
+       } else if (n != 2) {
+               min = 99; /* random crap */
+       } else if (*end == ':') {
+               /* hh:mm? */
+               min = strtoul(end + 1, &end, 10);
+               if (end - (date + 1) != 5)
+                       min = 99; /* random crap */
+       } /* otherwise we parsed "hh" */
 
        /*
-        * Don't accept any random crap.. At least 3 digits, and
-        * a valid minute. We might want to check that the minutes
-        * are divisible by 30 or something too.
+        * Don't accept any random crap. Even though some places have
+        * offset larger than 12 hours (e.g. Pacific/Kiritimati is at
+        * UTC+14), there is something wrong if hour part is much
+        * larger than that. We might also want to check that the
+        * minutes are divisible by 15 or something too. (Offset of
+        * Kathmandu, Nepal is UTC+5:45)
         */
-       if (min < 60 && n > 2) {
-               offset = hour*60+min;
+       if (min < 60 && hour < 24) {
+               int offset = hour * 60 + min;
                if (*date == '-')
                        offset = -offset;
-
                *offp = offset;
        }
        return end - date;
@@ -585,6 +597,33 @@ static int date_string(unsigned long date, int offset, char *buf, int len)
        return snprintf(buf, len, "%lu %c%02d%02d", date, sign, offset / 60, offset % 60);
 }
 
+/*
+ * Parse a string like "0 +0000" as ancient timestamp near epoch, but
+ * only when it appears not as part of any other string.
+ */
+static int match_object_header_date(const char *date, unsigned long *timestamp, int *offset)
+{
+       char *end;
+       unsigned long stamp;
+       int ofs;
+
+       if (*date < '0' || '9' <= *date)
+               return -1;
+       stamp = strtoul(date, &end, 10);
+       if (*end != ' ' || stamp == ULONG_MAX || (end[1] != '+' && end[1] != '-'))
+               return -1;
+       date = end + 2;
+       ofs = strtol(date, &end, 10);
+       if ((*end != '\0' && (*end != '\n')) || end != date + 4)
+               return -1;
+       ofs = (ofs / 100) * 60 + (ofs % 100);
+       if (date[-1] == '-')
+               ofs = -ofs;
+       *timestamp = stamp;
+       *offset = ofs;
+       return 0;
+}
+
 /* 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_basic(const char *date, unsigned long *timestamp, int *offset)
@@ -610,6 +649,9 @@ int parse_date_basic(const char *date, unsigned long *timestamp, int *offset)
        *offset = -1;
        tm_gmt = 0;
 
+       if (*date == '@' &&
+           !match_object_header_date(date + 1, timestamp, offset))
+               return 0; /* success */
        for (;;) {
                int match = 0;
                unsigned char c = *date;