date.c 18.5 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 405
			break;
		return 0;
	}
	return end - date;
}

/*
J
Junio C Hamano 已提交
406
 * We've seen a digit. Time? Year? Date?
407
 */
408
static int match_digit(const char *date, struct tm *tm, int *offset, int *tm_gmt)
409 410 411 412 413 414 415 416
{
	int n;
	char *end;
	unsigned long num;

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

	/*
417 418 419
	 * 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.
420
	 */
421
	if (num >= 100000000) {
422
		time_t time = num;
423 424
		if (gmtime_r(&time, tm)) {
			*tm_gmt = 1;
425
			return end - date;
426
		}
427 428 429
	}

	/*
430
	 * Check for special formats: num[-.:/]num[same]num
431 432 433
	 */
	switch (*end) {
	case ':':
434
	case '.':
435 436 437 438 439 440
	case '/':
	case '-':
		if (isdigit(end[1])) {
			int match = match_multi_number(num, *end, date, end, tm);
			if (match)
				return match;
441 442
		}
	}
443 444 445 446 447 448 449 450 451 452 453 454 455

	/*
	 * 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) {
456
		if (num <= 1400 && *offset == -1) {
457 458 459 460 461 462 463 464 465 466
			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;
	}

	/*
	 * NOTE! We will give precedence to day-of-month over month or
J
Junio C Hamano 已提交
467
	 * year numbers in the 1-12 range. So 05 is always "mday 5",
468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497
	 * 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 > 1900) {
		tm->tm_year = num - 1900;
	} else if (num > 70) {
		tm->tm_year = num;
	} else if (num > 0 && num < 13) {
		tm->tm_mon = num-1;
	}
J
Junio C Hamano 已提交
498

499
	return n;
500
}
501

502
static int match_tz(const char *date, int *offp)
503 504 505 506
{
	char *end;
	int offset = strtoul(date+1, &end, 10);
	int min, hour;
507
	int n = end - date - 1;
508

509 510
	min = offset % 100;
	hour = offset / 100;
511

512 513 514 515 516
	/*
	 * 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.
	 */
517 518 519 520
	if (min < 60 && n > 2) {
		offset = hour*60+min;
		if (*date == '-')
			offset = -offset;
521

522 523
		*offp = offset;
	}
524 525
	return end - date;
}
526

527 528 529 530 531 532 533 534 535 536 537
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);
}

538 539
/* 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. */
540
int parse_date(const char *date, char *result, int maxlen)
541 542
{
	struct tm tm;
543
	int offset, tm_gmt;
544
	time_t then;
545

546 547 548 549
	memset(&tm, 0, sizeof(tm));
	tm.tm_year = -1;
	tm.tm_mon = -1;
	tm.tm_mday = -1;
550 551
	tm.tm_isdst = -1;
	offset = -1;
552
	tm_gmt = 0;
553 554 555 556 557 558 559 560 561 562 563 564

	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))
565
			match = match_digit(date, &tm, &offset, &tm_gmt);
566 567 568 569 570 571
		else if ((c == '-' || c == '+') && isdigit(date[1]))
			match = match_tz(date, &offset);

		if (!match) {
			/* BAD CRAP */
			match = 1;
J
Junio C Hamano 已提交
572
		}
573 574 575

		date += match;
	}
576

577
	/* mktime uses local timezone */
578
	then = tm_to_time_t(&tm);
579 580 581
	if (offset == -1)
		offset = (then - mktime(&tm)) / 60;

582
	if (then == -1)
583
		return -1;
584

585 586
	if (!tm_gmt)
		then -= offset * 60;
587
	return date_string(then, offset, result, maxlen);
588 589
}

590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609
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);
}

610 611 612 613 614 615 616
void datestamp(char *buf, int bufsize)
{
	time_t now;
	int offset;

	time(&now);

617
	offset = tm_to_time_t(localtime(&now)) - now;
618 619
	offset /= 60;

620
	date_string(now, offset, buf, bufsize);
621
}
622 623 624 625 626 627 628

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

629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657
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);
}

658 659
static void date_pm(struct tm *tm, int *num)
{
660
	int hour, n = *num;
661 662
	*num = 0;

663 664 665
	hour = tm->tm_hour;
	if (n) {
		hour = n;
666 667 668
		tm->tm_min = 0;
		tm->tm_sec = 0;
	}
669
	tm->tm_hour = (hour % 12) + 12;
670 671 672 673
}

static void date_am(struct tm *tm, int *num)
{
674
	int hour, n = *num;
675 676
	*num = 0;

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

686 687
static void date_never(struct tm *tm, int *num)
{
688 689
	time_t n = 0;
	localtime_r(&n, tm);
690 691
}

692 693 694 695 696 697 698 699
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 },
700 701
	{ "PM", date_pm },
	{ "AM", date_am },
702
	{ "never", date_never },
703 704 705
	{ NULL }
};

706 707 708 709 710
static const char *number_name[] = {
	"zero", "one", "two", "three", "four",
	"five", "six", "seven", "eight", "nine", "ten",
};

711
static const struct typelen {
712 713 714 715 716 717 718 719 720
	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 已提交
721
};
722 723 724

static const char *approxidate_alpha(const char *date, struct tm *tm, int *num)
{
725 726
	const struct typelen *tl;
	const struct special *s;
727
	const char *end = date;
728
	int i;
729

730 731
	while (isalpha(*++end));
		;
732 733 734 735 736 737 738 739 740

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

741 742 743 744 745 746
	for (s = special; s->name; s++) {
		int len = strlen(s->name);
		if (match_string(date, s->name) == len) {
			s->fn(tm, num);
			return end;
		}
747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772
	}

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

773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788
	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;
		}
	}

789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808
	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;
}

809 810 811 812 813
static const char *approxidate_digit(const char *date, struct tm *tm, int *num)
{
	char *end;
	unsigned long number = strtoul(date, &end, 10);

814 815 816 817 818 819 820 821 822 823 824 825
	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;
		}
	}

826 827 828 829
	*num = number;
	return end;
}

830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848
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)) {
849
			date = approxidate_digit(date-1, &tm, &number);
850 851 852 853 854 855 856
			continue;
		}
		if (isalpha(c))
			date = approxidate_alpha(date-1, &tm, &number);
	}
	if (number > 0 && number < 32)
		tm.tm_mday = number;
L
Linus Torvalds 已提交
857
	if (tm.tm_mon > now.tm_mon && tm.tm_year == now.tm_year)
858 859 860
		tm.tm_year--;
	return mktime(&tm);
}