date.c 18.9 KB
Newer Older
1 2 3 4 5 6
/*
 * GIT - The information manager from hell
 *
 * Copyright (C) Linus Torvalds, 2005
 */

L
Linus Torvalds 已提交
7 8
#include "cache.h"

9 10 11 12
/*
 * This is like mktime, but without normalization of tm_wday and tm_yday.
 */
time_t tm_to_time_t(const struct tm *tm)
13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
{
	static const int mdays[] = {
	    0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334
	};
	int year = tm->tm_year - 70;
	int month = tm->tm_mon;
	int day = tm->tm_mday;

	if (year < 0 || year > 129) /* algo only works for 1970-2099 */
		return -1;
	if (month < 0 || month > 11) /* array bounds */
		return -1;
	if (month < 2 || (year + 2) % 4)
		day--;
	return (year * 365 + (year + 1) / 4 + mdays[month] + day) * 24*60*60UL +
		tm->tm_hour * 60*60 + tm->tm_min * 60 + tm->tm_sec;
}

static const char *month_names[] = {
32 33
	"January", "February", "March", "April", "May", "June",
	"July", "August", "September", "October", "November", "December"
34 35 36
};

static const char *weekday_names[] = {
37
	"Sundays", "Mondays", "Tuesdays", "Wednesdays", "Thursdays", "Fridays", "Saturdays"
38 39
};

L
Linus Torvalds 已提交
40 41 42 43 44 45 46 47 48 49
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;
}

50 51 52 53 54
/*
 * 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,
 * even though it means "sixty minutes off"
 */
55
static struct tm *time_to_tm(unsigned long time, int tz)
56
{
L
Linus Torvalds 已提交
57
	time_t t = gm_time_t(time, tz);
58 59 60
	return gmtime(&t);
}

61 62 63 64 65 66 67 68 69 70 71 72
/*
 * What value of "tz" was in effect back then at "time" in the
 * local timezone?
 */
static int local_tzoffset(unsigned long time)
{
	time_t t, t_local;
	struct tm tm;
	int offset, eastwest;

	t = time;
	localtime_r(&t, &tm);
73
	t_local = tm_to_time_t(&tm);
74 75 76 77 78 79 80 81 82 83 84 85 86

	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;
}

87
const char *show_date(unsigned long time, int tz, enum date_mode mode)
88 89 90 91
{
	struct tm *tm;
	static char timebuf[200];

92
	if (mode == DATE_RELATIVE) {
L
Linus Torvalds 已提交
93 94 95
		unsigned long diff;
		struct timeval now;
		gettimeofday(&now, NULL);
96
		if (now.tv_sec < time)
L
Linus Torvalds 已提交
97
			return "in the future";
98
		diff = now.tv_sec - time;
L
Linus Torvalds 已提交
99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133
		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.. */
	}

134 135 136
	if (mode == DATE_LOCAL)
		tz = local_tzoffset(time);

137
	tm = time_to_tm(time, tz);
138 139
	if (!tm)
		return NULL;
140 141 142
	if (mode == DATE_SHORT)
		sprintf(timebuf, "%04d-%02d-%02d", tm->tm_year + 1900,
				tm->tm_mon + 1, tm->tm_mday);
143 144 145 146 147 148 149
	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);
150 151 152 153 154
	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);
155
	else
156
		sprintf(timebuf, "%.3s %.3s %d %02d:%02d:%02d %d%c%+05d",
157 158 159 160
				weekday_names[tm->tm_wday],
				month_names[tm->tm_mon],
				tm->tm_mday,
				tm->tm_hour, tm->tm_min, tm->tm_sec,
161 162 163
				tm->tm_year + 1900,
				(mode == DATE_LOCAL) ? 0 : ' ',
				tz);
164 165 166
	return timebuf;
}

167 168 169 170 171 172 173 174 175
/*
 * Check these. And note how it doesn't do the summer-time conversion.
 *
 * In my world, it's always summer, and things are probably a bit off
 * in other ways too.
 */
static const struct {
	const char *name;
	int offset;
176
	int dst;
177
} timezone_names[] = {
178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210
	{ "IDLW", -12, 0, },	/* International Date Line West */
	{ "NT",   -11, 0, },	/* Nome */
	{ "CAT",  -10, 0, },	/* Central Alaska */
	{ "HST",  -10, 0, },	/* Hawaii Standard */
	{ "HDT",  -10, 1, },	/* Hawaii Daylight */
	{ "YST",   -9, 0, },	/* Yukon Standard */
	{ "YDT",   -9, 1, },	/* Yukon Daylight */
	{ "PST",   -8, 0, },	/* Pacific Standard */
	{ "PDT",   -8, 1, },	/* Pacific Daylight */
	{ "MST",   -7, 0, },	/* Mountain Standard */
	{ "MDT",   -7, 1, },	/* Mountain Daylight */
	{ "CST",   -6, 0, },	/* Central Standard */
	{ "CDT",   -6, 1, },	/* Central Daylight */
	{ "EST",   -5, 0, },	/* Eastern Standard */
	{ "EDT",   -5, 1, },	/* Eastern Daylight */
	{ "AST",   -3, 0, },	/* Atlantic Standard */
	{ "ADT",   -3, 1, },	/* Atlantic Daylight */
	{ "WAT",   -1, 0, },	/* West Africa */

	{ "GMT",    0, 0, },	/* Greenwich Mean */
	{ "UTC",    0, 0, },	/* Universal (Coordinated) */

	{ "WET",    0, 0, },	/* Western European */
	{ "BST",    0, 1, },	/* British Summer */
	{ "CET",   +1, 0, },	/* Central European */
	{ "MET",   +1, 0, },	/* Middle European */
	{ "MEWT",  +1, 0, },	/* Middle European Winter */
	{ "MEST",  +1, 1, },	/* Middle European Summer */
	{ "CEST",  +1, 1, },	/* Central European Summer */
	{ "MESZ",  +1, 1, },	/* Middle European Summer */
	{ "FWT",   +1, 0, },	/* French Winter */
	{ "FST",   +1, 1, },	/* French Summer */
	{ "EET",   +2, 0, },	/* Eastern Europe, USSR Zone 1 */
211
	{ "EEST",  +2, 1, },	/* Eastern European Daylight */
212 213 214 215 216 217 218
	{ "WAST",  +7, 0, },	/* West Australian Standard */
	{ "WADT",  +7, 1, },	/* West Australian Daylight */
	{ "CCT",   +8, 0, },	/* China Coast, USSR Zone 7 */
	{ "JST",   +9, 0, },	/* Japan Standard, USSR Zone 8 */
	{ "EAST", +10, 0, },	/* Eastern Australian Standard */
	{ "EADT", +10, 1, },	/* Eastern Australian Daylight */
	{ "GST",  +10, 0, },	/* Guam Standard, USSR Zone 9 */
219 220 221
	{ "NZT",  +12, 0, },	/* New Zealand */
	{ "NZST", +12, 0, },	/* New Zealand Standard */
	{ "NZDT", +12, 1, },	/* New Zealand Daylight */
222
	{ "IDLE", +12, 0, },	/* International Date Line East */
223
};
224

225
static int match_string(const char *date, const char *str)
226
{
227 228 229 230 231 232 233 234 235 236 237 238
	int i = 0;

	for (i = 0; *date; date++, str++, i++) {
		if (*date == *str)
			continue;
		if (toupper(*date) == toupper(*str))
			continue;
		if (!isalnum(*date))
			break;
		return 0;
	}
	return i;
239 240
}

241 242 243 244 245 246 247 248 249
static int skip_alpha(const char *date)
{
	int i = 0;
	do {
		i++;
	} while (isalpha(date[i]));
	return i;
}

250 251 252 253
/*
* Parse month, weekday, or timezone name
*/
static int match_alpha(const char *date, struct tm *tm, int *offset)
254
{
255
	int i;
256

257 258 259 260 261
	for (i = 0; i < 12; i++) {
		int match = match_string(date, month_names[i]);
		if (match >= 3) {
			tm->tm_mon = i;
			return match;
262
		}
263
	}
264

265 266 267 268 269 270 271
	for (i = 0; i < 7; i++) {
		int match = match_string(date, weekday_names[i]);
		if (match >= 3) {
			tm->tm_wday = i;
			return match;
		}
	}
272

273
	for (i = 0; i < ARRAY_SIZE(timezone_names); i++) {
274 275
		int match = match_string(date, timezone_names[i].name);
		if (match >= 3) {
276 277 278 279 280
			int off = timezone_names[i].offset;

			/* This is bogus, but we like summer */
			off += timezone_names[i].dst;

281 282 283 284
			/* Only use the tz name offset if we don't have anything better */
			if (*offset == -1)
				*offset = 60*off;

285
			return match;
286 287 288
		}
	}

289
	if (match_string(date, "PM") == 2) {
290 291 292 293 294 295
		tm->tm_hour = (tm->tm_hour % 12) + 12;
		return 2;
	}

	if (match_string(date, "AM") == 2) {
		tm->tm_hour = (tm->tm_hour % 12) + 0;
296 297 298
		return 2;
	}

299
	/* BAD CRAP */
300
	return skip_alpha(date);
301
}
302

303
static int is_date(int year, int month, int day, struct tm *now_tm, time_t now, struct tm *tm)
304
{
305
	if (month > 0 && month < 13 && day > 0 && day < 32) {
306 307 308 309 310 311
		struct tm check = *tm;
		struct tm *r = (now_tm ? &check : tm);
		time_t specified;

		r->tm_mon = month - 1;
		r->tm_mday = day;
312
		if (year == -1) {
313 314 315
			if (!now_tm)
				return 1;
			r->tm_year = now_tm->tm_year;
316
		}
317 318 319 320 321 322 323
		else if (year >= 1970 && year < 2100)
			r->tm_year = year - 1900;
		else if (year > 70 && year < 100)
			r->tm_year = year;
		else if (year < 38)
			r->tm_year = year + 100;
		else
324
			return 0;
325 326 327
		if (!now_tm)
			return 1;

328
		specified = tm_to_time_t(r);
329

330 331 332 333 334 335 336 337 338 339
		/* Be it commit time or author time, it does not make
		 * sense to specify timestamp way into the future.  Make
		 * sure it is not later than ten days from now...
		 */
		if (now + 10*24*3600 < specified)
			return 0;
		tm->tm_mon = r->tm_mon;
		tm->tm_mday = r->tm_mday;
		if (year != -1)
			tm->tm_year = r->tm_year;
340
		return 1;
341
	}
342 343
	return 0;
}
344

345
static int match_multi_number(unsigned long num, char c, const char *date, char *end, struct tm *tm)
346
{
347 348 349
	time_t now;
	struct tm now_tm;
	struct tm *refuse_future;
350 351 352 353 354 355 356 357
	long num2, num3;

	num2 = strtol(end+1, &end, 10);
	num3 = -1;
	if (*end == c && isdigit(end[1]))
		num3 = strtol(end+1, &end, 10);

	/* Time? Date? */
358
	switch (c) {
359 360 361 362 363 364 365
	case ':':
		if (num3 < 0)
			num3 = 0;
		if (num < 25 && num2 >= 0 && num2 < 60 && num3 >= 0 && num3 <= 60) {
			tm->tm_hour = num;
			tm->tm_min = num2;
			tm->tm_sec = num3;
366 367
			break;
		}
368
		return 0;
369 370 371

	case '-':
	case '/':
372 373 374 375 376 377
	case '.':
		now = time(NULL);
		refuse_future = NULL;
		if (gmtime_r(&now, &now_tm))
			refuse_future = &now_tm;

378 379
		if (num > 70) {
			/* yyyy-mm-dd? */
380
			if (is_date(num, num2, num3, refuse_future, now, tm))
381 382
				break;
			/* yyyy-dd-mm? */
383
			if (is_date(num, num3, num2, refuse_future, now, tm))
384
				break;
385
		}
386 387 388 389 390 391 392 393 394
		/* Our eastern European friends say dd.mm.yy[yy]
		 * is the norm there, so giving precedence to
		 * mm/dd/yy[yy] form only when separator is not '.'
		 */
		if (c != '.' &&
		    is_date(num3, num, num2, refuse_future, now, tm))
			break;
		/* European dd.mm.yy[yy] or funny US dd/mm/yy[yy] */
		if (is_date(num3, num2, num, refuse_future, now, tm))
395
			break;
396 397 398
		/* Funny European mm.dd.yy */
		if (c == '.' &&
		    is_date(num3, num, num2, refuse_future, now, tm))
399 400 401 402 403 404
			break;
		return 0;
	}
	return end - date;
}

405 406 407 408 409 410 411 412 413
/* 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);
}

414
/*
J
Junio C Hamano 已提交
415
 * We've seen a digit. Time? Year? Date?
416
 */
417
static int match_digit(const char *date, struct tm *tm, int *offset, int *tm_gmt)
418 419 420 421 422 423 424 425
{
	int n;
	char *end;
	unsigned long num;

	num = strtoul(date, &end, 10);

	/*
426 427 428
	 * 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.
429
	 */
430
	if (num >= 100000000 && nodate(tm)) {
431
		time_t time = num;
432 433
		if (gmtime_r(&time, tm)) {
			*tm_gmt = 1;
434
			return end - date;
435
		}
436 437 438
	}

	/*
439
	 * Check for special formats: num[-.:/]num[same]num
440 441 442
	 */
	switch (*end) {
	case ':':
443
	case '.':
444 445 446 447 448 449
	case '/':
	case '-':
		if (isdigit(end[1])) {
			int match = match_multi_number(num, *end, date, end, tm);
			if (match)
				return match;
450 451
		}
	}
452 453 454 455 456 457 458 459 460 461 462 463 464

	/*
	 * None of the special formats? Try to guess what
	 * the number meant. We use the number of digits
	 * to make a more educated guess..
	 */
	n = 0;
	do {
		n++;
	} while (isdigit(date[n]));

	/* Four-digit year or a timezone? */
	if (n == 4) {
465
		if (num <= 1400 && *offset == -1) {
466 467 468 469 470 471 472 473
			unsigned int minutes = num % 100;
			unsigned int hours = num / 100;
			*offset = hours*60 + minutes;
		} else if (num > 1900 && num < 2100)
			tm->tm_year = num - 1900;
		return n;
	}

474 475 476 477 478 479 480
	/*
	 * 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;

481 482
	/*
	 * NOTE! We will give precedence to day-of-month over month or
J
Junio C Hamano 已提交
483
	 * year numbers in the 1-12 range. So 05 is always "mday 5",
484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509
	 * unless we already have a mday..
	 *
	 * IOW, 01 Apr 05 parses as "April 1st, 2005".
	 */
	if (num > 0 && num < 32 && tm->tm_mday < 0) {
		tm->tm_mday = num;
		return n;
	}

	/* Two-digit year? */
	if (n == 2 && tm->tm_year < 0) {
		if (num < 10 && tm->tm_mday >= 0) {
			tm->tm_year = num + 100;
			return n;
		}
		if (num >= 70) {
			tm->tm_year = num;
			return n;
		}
	}

	if (num > 0 && num < 32) {
		tm->tm_mday = num;
	} else if (num > 0 && num < 13) {
		tm->tm_mon = num-1;
	}
J
Junio C Hamano 已提交
510

511
	return n;
512
}
513

514
static int match_tz(const char *date, int *offp)
515 516 517 518
{
	char *end;
	int offset = strtoul(date+1, &end, 10);
	int min, hour;
519
	int n = end - date - 1;
520

521 522
	min = offset % 100;
	hour = offset / 100;
523

524 525 526 527 528
	/*
	 * 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.
	 */
529 530 531 532
	if (min < 60 && n > 2) {
		offset = hour*60+min;
		if (*date == '-')
			offset = -offset;
533

534 535
		*offp = offset;
	}
536 537
	return end - date;
}
538

539 540 541 542 543 544 545 546 547 548 549
static int date_string(unsigned long date, int offset, char *buf, int len)
{
	int sign = '+';

	if (offset < 0) {
		offset = -offset;
		sign = '-';
	}
	return snprintf(buf, len, "%lu %c%02d%02d", date, sign, offset / 60, offset % 60);
}

550 551
/* 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. */
552
int parse_date(const char *date, char *result, int maxlen)
553 554
{
	struct tm tm;
555
	int offset, tm_gmt;
556
	time_t then;
557

558 559 560 561
	memset(&tm, 0, sizeof(tm));
	tm.tm_year = -1;
	tm.tm_mon = -1;
	tm.tm_mday = -1;
562 563
	tm.tm_isdst = -1;
	offset = -1;
564
	tm_gmt = 0;
565 566 567 568 569 570 571 572 573 574 575 576

	for (;;) {
		int match = 0;
		unsigned char c = *date;

		/* Stop at end of string or newline */
		if (!c || c == '\n')
			break;

		if (isalpha(c))
			match = match_alpha(date, &tm, &offset);
		else if (isdigit(c))
577
			match = match_digit(date, &tm, &offset, &tm_gmt);
578 579 580 581 582 583
		else if ((c == '-' || c == '+') && isdigit(date[1]))
			match = match_tz(date, &offset);

		if (!match) {
			/* BAD CRAP */
			match = 1;
J
Junio C Hamano 已提交
584
		}
585 586 587

		date += match;
	}
588

589
	/* mktime uses local timezone */
590
	then = tm_to_time_t(&tm);
591 592 593
	if (offset == -1)
		offset = (then - mktime(&tm)) / 60;

594
	if (then == -1)
595
		return -1;
596

597 598
	if (!tm_gmt)
		then -= offset * 60;
599
	return date_string(then, offset, result, maxlen);
600 601
}

602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621
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);
}

622 623 624 625 626 627 628
void datestamp(char *buf, int bufsize)
{
	time_t now;
	int offset;

	time(&now);

629
	offset = tm_to_time_t(localtime(&now)) - now;
630 631
	offset /= 60;

632
	date_string(now, offset, buf, bufsize);
633
}
634 635 636 637 638 639 640

static void update_tm(struct tm *tm, unsigned long sec)
{
	time_t n = mktime(tm) - sec;
	localtime_r(&n, tm);
}

641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669
static void date_yesterday(struct tm *tm, int *num)
{
	update_tm(tm, 24*60*60);
}

static void date_time(struct tm *tm, int hour)
{
	if (tm->tm_hour < hour)
		date_yesterday(tm, NULL);
	tm->tm_hour = hour;
	tm->tm_min = 0;
	tm->tm_sec = 0;
}

static void date_midnight(struct tm *tm, int *num)
{
	date_time(tm, 0);
}

static void date_noon(struct tm *tm, int *num)
{
	date_time(tm, 12);
}

static void date_tea(struct tm *tm, int *num)
{
	date_time(tm, 17);
}

670 671
static void date_pm(struct tm *tm, int *num)
{
672
	int hour, n = *num;
673 674
	*num = 0;

675 676 677
	hour = tm->tm_hour;
	if (n) {
		hour = n;
678 679 680
		tm->tm_min = 0;
		tm->tm_sec = 0;
	}
681
	tm->tm_hour = (hour % 12) + 12;
682 683 684 685
}

static void date_am(struct tm *tm, int *num)
{
686
	int hour, n = *num;
687 688
	*num = 0;

689 690 691
	hour = tm->tm_hour;
	if (n) {
		hour = n;
692 693 694
		tm->tm_min = 0;
		tm->tm_sec = 0;
	}
695
	tm->tm_hour = (hour % 12);
696 697
}

698 699
static void date_never(struct tm *tm, int *num)
{
700 701
	time_t n = 0;
	localtime_r(&n, tm);
702 703
}

704 705 706 707 708 709 710 711
static const struct special {
	const char *name;
	void (*fn)(struct tm *, int *);
} special[] = {
	{ "yesterday", date_yesterday },
	{ "noon", date_noon },
	{ "midnight", date_midnight },
	{ "tea", date_tea },
712 713
	{ "PM", date_pm },
	{ "AM", date_am },
714
	{ "never", date_never },
715 716 717
	{ NULL }
};

718 719 720 721 722
static const char *number_name[] = {
	"zero", "one", "two", "three", "four",
	"five", "six", "seven", "eight", "nine", "ten",
};

723
static const struct typelen {
724 725 726 727 728 729 730 731 732
	const char *type;
	int length;
} typelen[] = {
	{ "seconds", 1 },
	{ "minutes", 60 },
	{ "hours", 60*60 },
	{ "days", 24*60*60 },
	{ "weeks", 7*24*60*60 },
	{ NULL }
J
Junio C Hamano 已提交
733
};
734 735 736

static const char *approxidate_alpha(const char *date, struct tm *tm, int *num)
{
737 738
	const struct typelen *tl;
	const struct special *s;
739
	const char *end = date;
740
	int i;
741

742 743
	while (isalpha(*++end));
		;
744 745 746 747 748 749 750 751 752

	for (i = 0; i < 12; i++) {
		int match = match_string(date, month_names[i]);
		if (match >= 3) {
			tm->tm_mon = i;
			return end;
		}
	}

753 754 755 756 757 758
	for (s = special; s->name; s++) {
		int len = strlen(s->name);
		if (match_string(date, s->name) == len) {
			s->fn(tm, num);
			return end;
		}
759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784
	}

	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++;
	}

785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800
	for (i = 0; i < 7; i++) {
		int match = match_string(date, weekday_names[i]);
		if (match >= 3) {
			int diff, n = *num -1;
			*num = 0;

			diff = tm->tm_wday - i;
			if (diff <= 0)
				n++;
			diff += 7*n;

			update_tm(tm, diff * 24 * 60 * 60);
			return end;
		}
	}

801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820
	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;
}

821 822 823 824 825
static const char *approxidate_digit(const char *date, struct tm *tm, int *num)
{
	char *end;
	unsigned long number = strtoul(date, &end, 10);

826 827 828 829 830 831 832 833 834 835 836 837
	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;
		}
	}

838 839 840
	/* Accept zero-padding only for small numbers ("Dec 02", never "Dec 0002") */
	if (date[0] != '0' || end - date <= 2)
		*num = number;
841 842 843
	return end;
}

844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862
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)) {
863
			date = approxidate_digit(date-1, &tm, &number);
864 865 866 867 868 869 870
			continue;
		}
		if (isalpha(c))
			date = approxidate_alpha(date-1, &tm, &number);
	}
	if (number > 0 && number < 32)
		tm.tm_mday = number;
L
Linus Torvalds 已提交
871
	if (tm.tm_mon > now.tm_mon && tm.tm_year == now.tm_year)
872 873 874
		tm.tm_year--;
	return mktime(&tm);
}