git's rev-parse.c function show_datestring presumes gnu date

Ok. This is the insane patch to do this.

It really isn't very careful, and the reason I call it "approxidate()"
will become obvious when you look at the code. It is very liberal in what
it accepts, to the point where sometimes the results may not make a whole
lot of sense.

It accepts "last week" as a date string, by virtue of "last" parsing as
the number 1, and it totally ignoring superfluous fluff like "ago", so
"last week" ends up being exactly the same thing as "1 week ago". Fine so
far.

It has strange side effects: "last december" will actually parse as "Dec
1", which actually _does_ turn out right, because it will then notice that
it's not December yet, so it will decide that you must be talking about a
date last year. So it actually gets it right, but it's kind of for the
"wrong" reasons.

It also accepts the numbers 1..10 in string format ("one" .. "ten"), so
you can do "ten weeks ago" or "ten hours ago" and it will do the right
thing.

But it will do some really strange thigns too: the string "this will last
forever", will not recognize anyting but "last", which is recognized as
"1", which since it doesn't understand anything else it will think is the
day of the month. So if you do

	gitk --since="this will last forever"

the date will actually parse as the first day of the current month.

And it will parse the string "now" as "now", but only because it doesn't
understand it at all, and it makes everything relative to "now".

Similarly, it doesn't actually parse the "ago" or "from now", so "2 weeks
ago" is exactly the same as "2 weeks from now". It's the current date
minus 14 days.

But hey, it's probably better (and certainly faster) than depending on GNU
date. So now you can portably do things like

	gitk --since="two weeks and three days ago"
	git log --since="July 5"
	git-whatchanged --since="10 hours ago"
	git log --since="last october"

and it will actually do exactly what you thought it would do (I think). It
will count 17 days backwards, and it will do so even if you don't have GNU
date installed.

(I don't do "last monday" or similar yet, but I can extend it to that too
if people want).

It was kind of fun trying to write code that uses such totally relaxed
"understanding" of dates yet tries to get it right for the trivial cases.
The result should be mixed with a few strange preprocessor tricks, and be
submitted for the IOCCC ;)

Feel free to try it out, and see how many strange dates it gets right. Or
wrong.

And if you find some interesting (and valid - not "interesting" as in
"strange", but "interesting" as in "I'd be interested in actually doing
this) thing it gets wrong - usually by not understanding it and silently
just doing some strange things - please holler.

Now, as usual this certainly hasn't been getting a lot of testing. But my
code always works, no?

		Linus

Signed-off-by: Junio C Hamano <junkio@cox.net>
This commit is contained in:
Linus Torvalds 2005-11-14 19:29:06 -08:00 committed by Junio C Hamano
parent a8883288fa
commit 3c07b1d194
3 changed files with 126 additions and 14 deletions

View File

@ -259,6 +259,7 @@ extern void *read_object_with_reference(const unsigned char *sha1,
const char *show_date(unsigned long time, int timezone);
int parse_date(const char *date, char *buf, int bufsize);
void datestamp(char *buf, int bufsize);
unsigned long approxidate(const char *);
extern int setup_ident(void);
extern char *get_ident(const char *name, const char *email, const char *date_str);

124
date.c
View File

@ -5,6 +5,7 @@
*/
#include <time.h>
#include <sys/time.h>
#include "cache.h"
@ -460,3 +461,126 @@ void datestamp(char *buf, int bufsize)
date_string(now, offset, buf, bufsize);
}
static void update_tm(struct tm *tm, unsigned long sec)
{
time_t n = mktime(tm) - sec;
localtime_r(&n, tm);
}
static const char *number_name[] = {
"zero", "one", "two", "three", "four",
"five", "six", "seven", "eight", "nine", "ten",
};
static struct typelen {
const char *type;
int length;
} typelen[] = {
{ "seconds", 1 },
{ "minutes", 60 },
{ "hours", 60*60 },
{ "days", 24*60*60 },
{ "weeks", 7*24*60*60 },
{ NULL }
};
static const char *approxidate_alpha(const char *date, struct tm *tm, int *num)
{
struct typelen *tl;
const char *end = date;
int n = 1, i;
while (isalpha(*++end))
n++;
for (i = 0; i < 12; i++) {
int match = match_string(date, month_names[i]);
if (match >= 3) {
tm->tm_mon = i;
return end;
}
}
if (match_string(date, "yesterday") > 8) {
update_tm(tm, 24*60*60);
return end;
}
if (!*num) {
for (i = 1; i < 11; i++) {
int len = strlen(number_name[i]);
if (match_string(date, number_name[i]) == len) {
*num = i;
return end;
}
}
if (match_string(date, "last") == 4)
*num = 1;
return end;
}
tl = typelen;
while (tl->type) {
int len = strlen(tl->type);
if (match_string(date, tl->type) >= len-1) {
update_tm(tm, tl->length * *num);
*num = 0;
return end;
}
tl++;
}
if (match_string(date, "months") >= 5) {
int n = tm->tm_mon - *num;
*num = 0;
while (n < 0) {
n += 12;
tm->tm_year--;
}
tm->tm_mon = n;
return end;
}
if (match_string(date, "years") >= 4) {
tm->tm_year -= *num;
*num = 0;
return end;
}
return end;
}
unsigned long approxidate(const char *date)
{
int number = 0;
struct tm tm, now;
struct timeval tv;
char buffer[50];
if (parse_date(date, buffer, sizeof(buffer)) > 0)
return strtoul(buffer, NULL, 10);
gettimeofday(&tv, NULL);
localtime_r(&tv.tv_sec, &tm);
now = tm;
for (;;) {
unsigned char c = *date;
if (!c)
break;
date++;
if (isdigit(c)) {
char *end;
number = strtoul(date-1, &end, 10);
date = end;
continue;
}
if (isalpha(c))
date = approxidate_alpha(date-1, &tm, &number);
}
if (number > 0 && number < 32)
tm.tm_mday = number;
if (tm.tm_mon > now.tm_mon)
tm.tm_year--;
return mktime(&tm);
}

View File

@ -131,25 +131,12 @@ static int show_reference(const char *refname, const unsigned char *sha1)
static void show_datestring(const char *flag, const char *datestr)
{
FILE *date;
static char buffer[100];
static char cmd[1000];
int len;
/* date handling requires both flags and revs */
if ((filter & (DO_FLAGS | DO_REVS)) != (DO_FLAGS | DO_REVS))
return;
len = strlen(flag);
memcpy(buffer, flag, len);
snprintf(cmd, sizeof(cmd), "date --date=%s +%%s", sq_quote(datestr));
date = popen(cmd, "r");
if (!date || !fgets(buffer + len, sizeof(buffer) - len, date))
die("git-rev-list: bad date string");
pclose(date);
len = strlen(buffer);
if (buffer[len-1] == '\n')
buffer[--len] = 0;
snprintf(buffer, sizeof(buffer), "%s%lu", flag, approxidate(datestr));
show(buffer);
}