date.c 21.7 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
/*
 * This is like mktime, but without normalization of tm_wday and tm_yday.
 */
12
static time_t tm_to_time_t(const struct tm *tm)
13 14 15 16 17 18 19 20 21 22 23 24 25 26
{
	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--;
27 28
	if (tm->tm_hour < 0 || tm->tm_min < 0 || tm->tm_sec < 0)
		return -1;
29 30 31 32 33
	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[] = {
34 35
	"January", "February", "March", "April", "May", "June",
	"July", "August", "September", "October", "November", "December"
36 37 38
};

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

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

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

63 64 65 66 67 68 69 70 71 72 73 74
/*
 * 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);
75
	t_local = tm_to_time_t(&tm);
76 77 78 79 80 81 82 83 84 85 86 87 88

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

89 90 91 92 93 94 95 96 97 98 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
const char *show_date_relative(unsigned long time, int tz,
			       const struct timeval *now,
			       char *timebuf,
			       size_t timebuf_size)
{
	unsigned long diff;
	if (now->tv_sec < time)
		return "in the future";
	diff = now->tv_sec - time;
	if (diff < 90) {
		snprintf(timebuf, timebuf_size, "%lu seconds ago", diff);
		return timebuf;
	}
	/* Turn it into minutes */
	diff = (diff + 30) / 60;
	if (diff < 90) {
		snprintf(timebuf, timebuf_size, "%lu minutes ago", diff);
		return timebuf;
	}
	/* Turn it into hours */
	diff = (diff + 30) / 60;
	if (diff < 36) {
		snprintf(timebuf, timebuf_size, "%lu hours ago", diff);
		return timebuf;
	}
	/* We deal with number of days from here on */
	diff = (diff + 12) / 24;
	if (diff < 14) {
		snprintf(timebuf, timebuf_size, "%lu days ago", diff);
		return timebuf;
	}
	/* Say weeks for the past 10 weeks or so */
	if (diff < 70) {
		snprintf(timebuf, timebuf_size, "%lu weeks ago", (diff + 3) / 7);
		return timebuf;
	}
	/* Say months for the past 12 months or so */
J
Johan Sageryd 已提交
126
	if (diff < 365) {
127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149
		snprintf(timebuf, timebuf_size, "%lu months ago", (diff + 15) / 30);
		return timebuf;
	}
	/* Give years and months for 5 years or so */
	if (diff < 1825) {
		unsigned long years = diff / 365;
		unsigned long months = (diff % 365 + 15) / 30;
		int n;
		n = snprintf(timebuf, timebuf_size, "%lu year%s",
				years, (years > 1 ? "s" : ""));
		if (months)
			snprintf(timebuf + n, timebuf_size - n,
					", %lu month%s ago",
					months, (months > 1 ? "s" : ""));
		else
			snprintf(timebuf + n, timebuf_size - n, " ago");
		return timebuf;
	}
	/* Otherwise, just years. Centuries is probably overkill. */
	snprintf(timebuf, timebuf_size, "%lu years ago", (diff + 183) / 365);
	return timebuf;
}

150
const char *show_date(unsigned long time, int tz, enum date_mode mode)
151 152 153 154
{
	struct tm *tm;
	static char timebuf[200];

L
Linus Torvalds 已提交
155 156 157 158 159
	if (mode == DATE_RAW) {
		snprintf(timebuf, sizeof(timebuf), "%lu %+05d", time, tz);
		return timebuf;
	}

160
	if (mode == DATE_RELATIVE) {
L
Linus Torvalds 已提交
161 162
		struct timeval now;
		gettimeofday(&now, NULL);
163 164
		return show_date_relative(time, tz, &now,
					  timebuf, sizeof(timebuf));
L
Linus Torvalds 已提交
165 166
	}

167 168 169
	if (mode == DATE_LOCAL)
		tz = local_tzoffset(time);

170
	tm = time_to_tm(time, tz);
171 172
	if (!tm)
		return NULL;
173 174 175
	if (mode == DATE_SHORT)
		sprintf(timebuf, "%04d-%02d-%02d", tm->tm_year + 1900,
				tm->tm_mon + 1, tm->tm_mday);
176 177 178 179 180 181 182
	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);
183 184 185 186 187
	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);
188
	else
189
		sprintf(timebuf, "%.3s %.3s %d %02d:%02d:%02d %d%c%+05d",
190 191 192 193
				weekday_names[tm->tm_wday],
				month_names[tm->tm_mon],
				tm->tm_mday,
				tm->tm_hour, tm->tm_min, tm->tm_sec,
194 195 196
				tm->tm_year + 1900,
				(mode == DATE_LOCAL) ? 0 : ' ',
				tz);
197 198 199
	return timebuf;
}

200 201 202 203 204 205 206 207 208
/*
 * 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;
209
	int dst;
210
} timezone_names[] = {
211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243
	{ "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 */
244
	{ "EEST",  +2, 1, },	/* Eastern European Daylight */
245 246 247 248 249 250 251
	{ "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 */
252 253 254
	{ "NZT",  +12, 0, },	/* New Zealand */
	{ "NZST", +12, 0, },	/* New Zealand Standard */
	{ "NZDT", +12, 1, },	/* New Zealand Daylight */
255
	{ "IDLE", +12, 0, },	/* International Date Line East */
256
};
257

258
static int match_string(const char *date, const char *str)
259
{
260 261 262 263 264 265 266 267 268 269 270 271
	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;
272 273
}

274 275 276 277 278 279 280 281 282
static int skip_alpha(const char *date)
{
	int i = 0;
	do {
		i++;
	} while (isalpha(date[i]));
	return i;
}

283 284 285 286
/*
* Parse month, weekday, or timezone name
*/
static int match_alpha(const char *date, struct tm *tm, int *offset)
287
{
288
	int i;
289

290 291 292 293 294
	for (i = 0; i < 12; i++) {
		int match = match_string(date, month_names[i]);
		if (match >= 3) {
			tm->tm_mon = i;
			return match;
295
		}
296
	}
297

298 299 300 301 302 303 304
	for (i = 0; i < 7; i++) {
		int match = match_string(date, weekday_names[i]);
		if (match >= 3) {
			tm->tm_wday = i;
			return match;
		}
	}
305

306
	for (i = 0; i < ARRAY_SIZE(timezone_names); i++) {
307 308
		int match = match_string(date, timezone_names[i].name);
		if (match >= 3) {
309 310 311 312 313
			int off = timezone_names[i].offset;

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

314 315 316 317
			/* Only use the tz name offset if we don't have anything better */
			if (*offset == -1)
				*offset = 60*off;

318
			return match;
319 320 321
		}
	}

322
	if (match_string(date, "PM") == 2) {
323 324 325 326 327 328
		tm->tm_hour = (tm->tm_hour % 12) + 12;
		return 2;
	}

	if (match_string(date, "AM") == 2) {
		tm->tm_hour = (tm->tm_hour % 12) + 0;
329 330 331
		return 2;
	}

332
	/* BAD CRAP */
333
	return skip_alpha(date);
334
}
335

336
static int is_date(int year, int month, int day, struct tm *now_tm, time_t now, struct tm *tm)
337
{
338
	if (month > 0 && month < 13 && day > 0 && day < 32) {
339 340 341 342 343 344
		struct tm check = *tm;
		struct tm *r = (now_tm ? &check : tm);
		time_t specified;

		r->tm_mon = month - 1;
		r->tm_mday = day;
345
		if (year == -1) {
346 347 348
			if (!now_tm)
				return 1;
			r->tm_year = now_tm->tm_year;
349
		}
350 351 352 353 354 355 356
		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
357
			return 0;
358 359 360
		if (!now_tm)
			return 1;

361
		specified = tm_to_time_t(r);
362

363 364 365 366 367 368 369 370 371 372
		/* 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;
373
		return 1;
374
	}
375 376
	return 0;
}
377

378
static int match_multi_number(unsigned long num, char c, const char *date, char *end, struct tm *tm)
379
{
380 381 382
	time_t now;
	struct tm now_tm;
	struct tm *refuse_future;
383 384 385 386 387 388 389 390
	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? */
391
	switch (c) {
392 393 394 395 396 397 398
	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;
399 400
			break;
		}
401
		return 0;
402 403 404

	case '-':
	case '/':
405 406 407 408 409 410
	case '.':
		now = time(NULL);
		refuse_future = NULL;
		if (gmtime_r(&now, &now_tm))
			refuse_future = &now_tm;

411 412
		if (num > 70) {
			/* yyyy-mm-dd? */
413
			if (is_date(num, num2, num3, refuse_future, now, tm))
414 415
				break;
			/* yyyy-dd-mm? */
416
			if (is_date(num, num3, num2, refuse_future, now, tm))
417
				break;
418
		}
419 420 421 422 423 424 425 426 427
		/* 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))
428
			break;
429 430 431
		/* Funny European mm.dd.yy */
		if (c == '.' &&
		    is_date(num3, num, num2, refuse_future, now, tm))
432 433 434 435 436 437
			break;
		return 0;
	}
	return end - date;
}

438 439 440 441 442
/*
 * Have we filled in any part of the time/date yet?
 * We just do a binary 'and' to see if the sign bit
 * is set in all the values.
 */
443 444
static inline int nodate(struct tm *tm)
{
445 446 447 448 449 450
	return (tm->tm_year &
		tm->tm_mon &
		tm->tm_mday &
		tm->tm_hour &
		tm->tm_min &
		tm->tm_sec) < 0;
451 452
}

453
/*
J
Junio C Hamano 已提交
454
 * We've seen a digit. Time? Year? Date?
455
 */
456
static int match_digit(const char *date, struct tm *tm, int *offset, int *tm_gmt)
457 458 459 460 461 462 463 464
{
	int n;
	char *end;
	unsigned long num;

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

	/*
465 466 467
	 * 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.
468
	 */
469
	if (num >= 100000000 && nodate(tm)) {
470
		time_t time = num;
471 472
		if (gmtime_r(&time, tm)) {
			*tm_gmt = 1;
473
			return end - date;
474
		}
475 476 477
	}

	/*
478
	 * Check for special formats: num[-.:/]num[same]num
479 480 481
	 */
	switch (*end) {
	case ':':
482
	case '.':
483 484 485 486 487 488
	case '/':
	case '-':
		if (isdigit(end[1])) {
			int match = match_multi_number(num, *end, date, end, tm);
			if (match)
				return match;
489 490
		}
	}
491 492 493 494 495 496 497 498 499 500 501 502 503

	/*
	 * 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) {
504
		if (num <= 1400 && *offset == -1) {
505 506 507 508 509 510 511 512
			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;
	}

513 514 515 516 517 518 519
	/*
	 * 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;

520 521
	/*
	 * NOTE! We will give precedence to day-of-month over month or
J
Junio C Hamano 已提交
522
	 * year numbers in the 1-12 range. So 05 is always "mday 5",
523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543
	 * 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;
		}
	}

L
Linus Torvalds 已提交
544
	if (num > 0 && num < 13 && tm->tm_mon < 0)
545
		tm->tm_mon = num-1;
J
Junio C Hamano 已提交
546

547
	return n;
548
}
549

550
static int match_tz(const char *date, int *offp)
551 552 553 554
{
	char *end;
	int offset = strtoul(date+1, &end, 10);
	int min, hour;
555
	int n = end - date - 1;
556

557 558
	min = offset % 100;
	hour = offset / 100;
559

560 561 562 563 564
	/*
	 * 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.
	 */
565 566 567 568
	if (min < 60 && n > 2) {
		offset = hour*60+min;
		if (*date == '-')
			offset = -offset;
569

570 571
		*offp = offset;
	}
572 573
	return end - date;
}
574

575 576 577 578 579 580 581 582 583 584 585
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);
}

586 587
/* 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. */
588
int parse_date(const char *date, char *result, int maxlen)
589 590
{
	struct tm tm;
591
	int offset, tm_gmt;
592
	time_t then;
593

594 595 596 597
	memset(&tm, 0, sizeof(tm));
	tm.tm_year = -1;
	tm.tm_mon = -1;
	tm.tm_mday = -1;
598
	tm.tm_isdst = -1;
599 600 601
	tm.tm_hour = -1;
	tm.tm_min = -1;
	tm.tm_sec = -1;
602
	offset = -1;
603
	tm_gmt = 0;
604 605 606 607 608 609 610 611 612 613 614 615

	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))
616
			match = match_digit(date, &tm, &offset, &tm_gmt);
617 618 619 620 621 622
		else if ((c == '-' || c == '+') && isdigit(date[1]))
			match = match_tz(date, &offset);

		if (!match) {
			/* BAD CRAP */
			match = 1;
J
Junio C Hamano 已提交
623
		}
624 625 626

		date += match;
	}
627

628
	/* mktime uses local timezone */
629
	then = tm_to_time_t(&tm);
630 631 632
	if (offset == -1)
		offset = (then - mktime(&tm)) / 60;

633
	if (then == -1)
634
		return -1;
635

636 637
	if (!tm_gmt)
		then -= offset * 60;
638
	return date_string(then, offset, result, maxlen);
639 640
}

641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656
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;
L
Linus Torvalds 已提交
657 658
	else if (!strcmp(format, "raw"))
		return DATE_RAW;
659 660 661 662
	else
		die("unknown date format %s", format);
}

663 664 665 666 667 668 669
void datestamp(char *buf, int bufsize)
{
	time_t now;
	int offset;

	time(&now);

670
	offset = tm_to_time_t(localtime(&now)) - now;
671 672
	offset /= 60;

673
	date_string(now, offset, buf, bufsize);
674
}
675

L
Linus Torvalds 已提交
676 677 678 679 680
/*
 * Relative time update (eg "2 days ago").  If we haven't set the time
 * yet, we need to set it from current time.
 */
static unsigned long update_tm(struct tm *tm, struct tm *now, unsigned long sec)
681
{
L
Linus Torvalds 已提交
682 683 684 685 686 687 688 689 690 691 692 693 694
	time_t n;

	if (tm->tm_mday < 0)
		tm->tm_mday = now->tm_mday;
	if (tm->tm_mon < 0)
		tm->tm_mon = now->tm_mon;
	if (tm->tm_year < 0) {
		tm->tm_year = now->tm_year;
		if (tm->tm_mon > now->tm_mon)
			tm->tm_year--;
	}

	n = mktime(tm) - sec;
695
	localtime_r(&n, tm);
L
Linus Torvalds 已提交
696
	return n;
697 698
}

L
Linus Torvalds 已提交
699
static void date_yesterday(struct tm *tm, struct tm *now, int *num)
700
{
L
Linus Torvalds 已提交
701
	update_tm(tm, now, 24*60*60);
702 703
}

L
Linus Torvalds 已提交
704
static void date_time(struct tm *tm, struct tm *now, int hour)
705 706
{
	if (tm->tm_hour < hour)
L
Linus Torvalds 已提交
707
		date_yesterday(tm, now, NULL);
708 709 710 711 712
	tm->tm_hour = hour;
	tm->tm_min = 0;
	tm->tm_sec = 0;
}

L
Linus Torvalds 已提交
713
static void date_midnight(struct tm *tm, struct tm *now, int *num)
714
{
L
Linus Torvalds 已提交
715
	date_time(tm, now, 0);
716 717
}

L
Linus Torvalds 已提交
718
static void date_noon(struct tm *tm, struct tm *now, int *num)
719
{
L
Linus Torvalds 已提交
720
	date_time(tm, now, 12);
721 722
}

L
Linus Torvalds 已提交
723
static void date_tea(struct tm *tm, struct tm *now, int *num)
724
{
L
Linus Torvalds 已提交
725
	date_time(tm, now, 17);
726 727
}

L
Linus Torvalds 已提交
728
static void date_pm(struct tm *tm, struct tm *now, int *num)
729
{
730
	int hour, n = *num;
731 732
	*num = 0;

733 734 735
	hour = tm->tm_hour;
	if (n) {
		hour = n;
736 737 738
		tm->tm_min = 0;
		tm->tm_sec = 0;
	}
739
	tm->tm_hour = (hour % 12) + 12;
740 741
}

L
Linus Torvalds 已提交
742
static void date_am(struct tm *tm, struct tm *now, int *num)
743
{
744
	int hour, n = *num;
745 746
	*num = 0;

747 748 749
	hour = tm->tm_hour;
	if (n) {
		hour = n;
750 751 752
		tm->tm_min = 0;
		tm->tm_sec = 0;
	}
753
	tm->tm_hour = (hour % 12);
754 755
}

L
Linus Torvalds 已提交
756
static void date_never(struct tm *tm, struct tm *now, int *num)
757
{
758 759
	time_t n = 0;
	localtime_r(&n, tm);
760 761
}

762 763
static const struct special {
	const char *name;
L
Linus Torvalds 已提交
764
	void (*fn)(struct tm *, struct tm *, int *);
765 766 767 768 769
} special[] = {
	{ "yesterday", date_yesterday },
	{ "noon", date_noon },
	{ "midnight", date_midnight },
	{ "tea", date_tea },
770 771
	{ "PM", date_pm },
	{ "AM", date_am },
772
	{ "never", date_never },
773 774 775
	{ NULL }
};

776 777 778 779 780
static const char *number_name[] = {
	"zero", "one", "two", "three", "four",
	"five", "six", "seven", "eight", "nine", "ten",
};

781
static const struct typelen {
782 783 784 785 786 787 788 789 790
	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 已提交
791
};
792

L
Linus Torvalds 已提交
793
static const char *approxidate_alpha(const char *date, struct tm *tm, struct tm *now, int *num)
794
{
795 796
	const struct typelen *tl;
	const struct special *s;
797
	const char *end = date;
798
	int i;
799

800 801
	while (isalpha(*++end));
		;
802 803 804 805 806 807 808 809 810

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

811 812 813
	for (s = special; s->name; s++) {
		int len = strlen(s->name);
		if (match_string(date, s->name) == len) {
L
Linus Torvalds 已提交
814
			s->fn(tm, now, num);
815 816
			return end;
		}
817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835
	}

	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) {
L
Linus Torvalds 已提交
836
			update_tm(tm, now, tl->length * *num);
837 838 839 840 841 842
			*num = 0;
			return end;
		}
		tl++;
	}

843 844 845 846 847 848 849 850 851 852 853
	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;

L
Linus Torvalds 已提交
854
			update_tm(tm, now, diff * 24 * 60 * 60);
855 856 857 858
			return end;
		}
	}

859
	if (match_string(date, "months") >= 5) {
860 861 862
		int n;
		update_tm(tm, now, 0); /* fill in date fields if needed */
		n = tm->tm_mon - *num;
863 864 865 866 867 868 869 870 871 872
		*num = 0;
		while (n < 0) {
			n += 12;
			tm->tm_year--;
		}
		tm->tm_mon = n;
		return end;
	}

	if (match_string(date, "years") >= 4) {
873
		update_tm(tm, now, 0); /* fill in date fields if needed */
874 875 876 877 878 879 880 881
		tm->tm_year -= *num;
		*num = 0;
		return end;
	}

	return end;
}

882 883 884 885 886
static const char *approxidate_digit(const char *date, struct tm *tm, int *num)
{
	char *end;
	unsigned long number = strtoul(date, &end, 10);

887 888 889 890 891 892 893 894 895 896 897 898
	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;
		}
	}

899 900 901
	/* Accept zero-padding only for small numbers ("Dec 02", never "Dec 0002") */
	if (date[0] != '0' || end - date <= 2)
		*num = number;
902 903 904
	return end;
}

L
Linus Torvalds 已提交
905 906 907 908 909 910 911 912 913 914 915 916 917
/*
 * Do we have a pending number at the end, or when
 * we see a new one? Let's assume it's a month day,
 * as in "Dec 6, 1992"
 */
static void pending_number(struct tm *tm, int *num)
{
	int number = *num;

	if (number) {
		*num = 0;
		if (tm->tm_mday < 0 && number < 32)
			tm->tm_mday = number;
918 919 920 921 922 923 924 925 926 927 928
		else if (tm->tm_mon < 0 && number < 13)
			tm->tm_mon = number-1;
		else if (tm->tm_year < 0) {
			if (number > 1969 && number < 2100)
				tm->tm_year = number - 1900;
			else if (number > 69 && number < 100)
				tm->tm_year = number;
			else if (number < 38)
				tm->tm_year = 100 + number;
			/* We screw up for number = 00 ? */
		}
L
Linus Torvalds 已提交
929 930 931
	}
}

932
static unsigned long approxidate_str(const char *date, const struct timeval *tv)
933 934 935
{
	int number = 0;
	struct tm tm, now;
936
	time_t time_sec;
937

938
	time_sec = tv->tv_sec;
939
	localtime_r(&time_sec, &tm);
940
	now = tm;
L
Linus Torvalds 已提交
941 942 943 944 945

	tm.tm_year = -1;
	tm.tm_mon = -1;
	tm.tm_mday = -1;

946 947 948 949 950 951
	for (;;) {
		unsigned char c = *date;
		if (!c)
			break;
		date++;
		if (isdigit(c)) {
L
Linus Torvalds 已提交
952
			pending_number(&tm, &number);
953
			date = approxidate_digit(date-1, &tm, &number);
954 955 956
			continue;
		}
		if (isalpha(c))
L
Linus Torvalds 已提交
957
			date = approxidate_alpha(date-1, &tm, &now, &number);
958
	}
L
Linus Torvalds 已提交
959 960
	pending_number(&tm, &number);
	return update_tm(&tm, &now, 0);
961
}
962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983

unsigned long approxidate_relative(const char *date, const struct timeval *tv)
{
	char buffer[50];

	if (parse_date(date, buffer, sizeof(buffer)) > 0)
		return strtoul(buffer, NULL, 0);

	return approxidate_str(date, tv);
}

unsigned long approxidate(const char *date)
{
	struct timeval tv;
	char buffer[50];

	if (parse_date(date, buffer, sizeof(buffer)) > 0)
		return strtoul(buffer, NULL, 0);

	gettimeofday(&tv, NULL);
	return approxidate_str(date, &tv);
}