Merge branch 'lt/approxidate'

* lt/approxidate:
  fix approxidate parsing of relative months and years
  tests: add date printing and parsing tests
  refactor test-date interface
  Add date formatting and parsing functions relative to a given time
  Further 'approxidate' improvements
  Improve on 'approxidate'

Conflicts:
	date.c
This commit is contained in:
Junio C Hamano 2009-08-31 22:11:36 -07:00
commit 554555ac7d
4 changed files with 318 additions and 109 deletions

View File

@ -731,9 +731,14 @@ enum date_mode {
}; };
const char *show_date(unsigned long time, int timezone, enum date_mode mode); const char *show_date(unsigned long time, int timezone, enum date_mode mode);
const char *show_date_relative(unsigned long time, int tz,
const struct timeval *now,
char *timebuf,
size_t timebuf_size);
int parse_date(const char *date, char *buf, int bufsize); int parse_date(const char *date, char *buf, int bufsize);
void datestamp(char *buf, int bufsize); void datestamp(char *buf, int bufsize);
unsigned long approxidate(const char *); unsigned long approxidate(const char *);
unsigned long approxidate_relative(const char *date, const struct timeval *now);
enum date_mode parse_date_format(const char *format); enum date_mode parse_date_format(const char *format);
#define IDENT_WARN_ON_NO_NAME 1 #define IDENT_WARN_ON_NO_NAME 1

280
date.c
View File

@ -24,6 +24,8 @@ time_t tm_to_time_t(const struct tm *tm)
return -1; return -1;
if (month < 2 || (year + 2) % 4) if (month < 2 || (year + 2) % 4)
day--; day--;
if (tm->tm_hour < 0 || tm->tm_min < 0 || tm->tm_sec < 0)
return -1;
return (year * 365 + (year + 1) / 4 + mdays[month] + day) * 24*60*60UL + return (year * 365 + (year + 1) / 4 + mdays[month] + day) * 24*60*60UL +
tm->tm_hour * 60*60 + tm->tm_min * 60 + tm->tm_sec; tm->tm_hour * 60*60 + tm->tm_min * 60 + tm->tm_sec;
} }
@ -84,6 +86,67 @@ static int local_tzoffset(unsigned long time)
return offset * eastwest; return offset * eastwest;
} }
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 */
if (diff < 360) {
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;
}
const char *show_date(unsigned long time, int tz, enum date_mode mode) const char *show_date(unsigned long time, int tz, enum date_mode mode)
{ {
struct tm *tm; struct tm *tm;
@ -95,63 +158,10 @@ const char *show_date(unsigned long time, int tz, enum date_mode mode)
} }
if (mode == DATE_RELATIVE) { if (mode == DATE_RELATIVE) {
unsigned long diff;
struct timeval now; struct timeval now;
gettimeofday(&now, NULL); gettimeofday(&now, NULL);
if (now.tv_sec < time) return show_date_relative(time, tz, &now,
return "in the future"; timebuf, sizeof(timebuf));
diff = now.tv_sec - time;
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;
}
/* 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, sizeof(timebuf), "%lu year%s",
years, (years > 1 ? "s" : ""));
if (months)
snprintf(timebuf + n, sizeof(timebuf) - n,
", %lu month%s ago",
months, (months > 1 ? "s" : ""));
else
snprintf(timebuf + n, sizeof(timebuf) - n,
" ago");
return timebuf;
}
/* Otherwise, just years. Centuries is probably overkill. */
snprintf(timebuf, sizeof(timebuf), "%lu years ago", (diff + 183) / 365);
return timebuf;
} }
if (mode == DATE_LOCAL) if (mode == DATE_LOCAL)
@ -425,13 +435,19 @@ static int match_multi_number(unsigned long num, char c, const char *date, char
return end - date; return end - date;
} }
/* Have we filled in any part of the time/date yet? */ /*
* 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.
*/
static inline int nodate(struct tm *tm) static inline int nodate(struct tm *tm)
{ {
return tm->tm_year < 0 && return (tm->tm_year &
tm->tm_mon < 0 && tm->tm_mon &
tm->tm_mday < 0 && tm->tm_mday &
!(tm->tm_hour | tm->tm_min | tm->tm_sec); tm->tm_hour &
tm->tm_min &
tm->tm_sec) < 0;
} }
/* /*
@ -525,11 +541,8 @@ static int match_digit(const char *date, struct tm *tm, int *offset, int *tm_gmt
} }
} }
if (num > 0 && num < 32) { if (num > 0 && num < 13 && tm->tm_mon < 0)
tm->tm_mday = num;
} else if (num > 0 && num < 13) {
tm->tm_mon = num-1; tm->tm_mon = num-1;
}
return n; return n;
} }
@ -583,6 +596,9 @@ int parse_date(const char *date, char *result, int maxlen)
tm.tm_mon = -1; tm.tm_mon = -1;
tm.tm_mday = -1; tm.tm_mday = -1;
tm.tm_isdst = -1; tm.tm_isdst = -1;
tm.tm_hour = -1;
tm.tm_min = -1;
tm.tm_sec = -1;
offset = -1; offset = -1;
tm_gmt = 0; tm_gmt = 0;
@ -657,42 +673,59 @@ void datestamp(char *buf, int bufsize)
date_string(now, offset, buf, bufsize); date_string(now, offset, buf, bufsize);
} }
static void update_tm(struct tm *tm, unsigned long sec) /*
* 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)
{ {
time_t n = mktime(tm) - sec; 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;
localtime_r(&n, tm); localtime_r(&n, tm);
return n;
} }
static void date_yesterday(struct tm *tm, int *num) static void date_yesterday(struct tm *tm, struct tm *now, int *num)
{ {
update_tm(tm, 24*60*60); update_tm(tm, now, 24*60*60);
} }
static void date_time(struct tm *tm, int hour) static void date_time(struct tm *tm, struct tm *now, int hour)
{ {
if (tm->tm_hour < hour) if (tm->tm_hour < hour)
date_yesterday(tm, NULL); date_yesterday(tm, now, NULL);
tm->tm_hour = hour; tm->tm_hour = hour;
tm->tm_min = 0; tm->tm_min = 0;
tm->tm_sec = 0; tm->tm_sec = 0;
} }
static void date_midnight(struct tm *tm, int *num) static void date_midnight(struct tm *tm, struct tm *now, int *num)
{ {
date_time(tm, 0); date_time(tm, now, 0);
} }
static void date_noon(struct tm *tm, int *num) static void date_noon(struct tm *tm, struct tm *now, int *num)
{ {
date_time(tm, 12); date_time(tm, now, 12);
} }
static void date_tea(struct tm *tm, int *num) static void date_tea(struct tm *tm, struct tm *now, int *num)
{ {
date_time(tm, 17); date_time(tm, now, 17);
} }
static void date_pm(struct tm *tm, int *num) static void date_pm(struct tm *tm, struct tm *now, int *num)
{ {
int hour, n = *num; int hour, n = *num;
*num = 0; *num = 0;
@ -706,7 +739,7 @@ static void date_pm(struct tm *tm, int *num)
tm->tm_hour = (hour % 12) + 12; tm->tm_hour = (hour % 12) + 12;
} }
static void date_am(struct tm *tm, int *num) static void date_am(struct tm *tm, struct tm *now, int *num)
{ {
int hour, n = *num; int hour, n = *num;
*num = 0; *num = 0;
@ -720,7 +753,7 @@ static void date_am(struct tm *tm, int *num)
tm->tm_hour = (hour % 12); tm->tm_hour = (hour % 12);
} }
static void date_never(struct tm *tm, int *num) static void date_never(struct tm *tm, struct tm *now, int *num)
{ {
time_t n = 0; time_t n = 0;
localtime_r(&n, tm); localtime_r(&n, tm);
@ -728,7 +761,7 @@ static void date_never(struct tm *tm, int *num)
static const struct special { static const struct special {
const char *name; const char *name;
void (*fn)(struct tm *, int *); void (*fn)(struct tm *, struct tm *, int *);
} special[] = { } special[] = {
{ "yesterday", date_yesterday }, { "yesterday", date_yesterday },
{ "noon", date_noon }, { "noon", date_noon },
@ -757,7 +790,7 @@ static const struct typelen {
{ NULL } { NULL }
}; };
static const char *approxidate_alpha(const char *date, struct tm *tm, int *num) static const char *approxidate_alpha(const char *date, struct tm *tm, struct tm *now, int *num)
{ {
const struct typelen *tl; const struct typelen *tl;
const struct special *s; const struct special *s;
@ -778,7 +811,7 @@ static const char *approxidate_alpha(const char *date, struct tm *tm, int *num)
for (s = special; s->name; s++) { for (s = special; s->name; s++) {
int len = strlen(s->name); int len = strlen(s->name);
if (match_string(date, s->name) == len) { if (match_string(date, s->name) == len) {
s->fn(tm, num); s->fn(tm, now, num);
return end; return end;
} }
} }
@ -800,7 +833,7 @@ static const char *approxidate_alpha(const char *date, struct tm *tm, int *num)
while (tl->type) { while (tl->type) {
int len = strlen(tl->type); int len = strlen(tl->type);
if (match_string(date, tl->type) >= len-1) { if (match_string(date, tl->type) >= len-1) {
update_tm(tm, tl->length * *num); update_tm(tm, now, tl->length * *num);
*num = 0; *num = 0;
return end; return end;
} }
@ -818,13 +851,15 @@ static const char *approxidate_alpha(const char *date, struct tm *tm, int *num)
n++; n++;
diff += 7*n; diff += 7*n;
update_tm(tm, diff * 24 * 60 * 60); update_tm(tm, now, diff * 24 * 60 * 60);
return end; return end;
} }
} }
if (match_string(date, "months") >= 5) { if (match_string(date, "months") >= 5) {
int n = tm->tm_mon - *num; int n;
update_tm(tm, now, 0); /* fill in date fields if needed */
n = tm->tm_mon - *num;
*num = 0; *num = 0;
while (n < 0) { while (n < 0) {
n += 12; n += 12;
@ -835,6 +870,7 @@ static const char *approxidate_alpha(const char *date, struct tm *tm, int *num)
} }
if (match_string(date, "years") >= 4) { if (match_string(date, "years") >= 4) {
update_tm(tm, now, 0); /* fill in date fields if needed */
tm->tm_year -= *num; tm->tm_year -= *num;
*num = 0; *num = 0;
return end; return end;
@ -866,36 +902,82 @@ static const char *approxidate_digit(const char *date, struct tm *tm, int *num)
return end; return end;
} }
unsigned long approxidate(const char *date) /*
* 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;
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 ? */
}
}
}
static unsigned long approxidate_str(const char *date, const struct timeval *tv)
{ {
int number = 0; int number = 0;
struct tm tm, now; struct tm tm, now;
struct timeval tv;
time_t time_sec; time_t time_sec;
char buffer[50];
if (parse_date(date, buffer, sizeof(buffer)) > 0) time_sec = tv->tv_sec;
return strtoul(buffer, NULL, 10);
gettimeofday(&tv, NULL);
time_sec = tv.tv_sec;
localtime_r(&time_sec, &tm); localtime_r(&time_sec, &tm);
now = tm; now = tm;
tm.tm_year = -1;
tm.tm_mon = -1;
tm.tm_mday = -1;
for (;;) { for (;;) {
unsigned char c = *date; unsigned char c = *date;
if (!c) if (!c)
break; break;
date++; date++;
if (isdigit(c)) { if (isdigit(c)) {
pending_number(&tm, &number);
date = approxidate_digit(date-1, &tm, &number); date = approxidate_digit(date-1, &tm, &number);
continue; continue;
} }
if (isalpha(c)) if (isalpha(c))
date = approxidate_alpha(date-1, &tm, &number); date = approxidate_alpha(date-1, &tm, &now, &number);
} }
if (number > 0 && number < 32) pending_number(&tm, &number);
tm.tm_mday = number; return update_tm(&tm, &now, 0);
if (tm.tm_mon > now.tm_mon && tm.tm_year == now.tm_year) }
tm.tm_year--;
return mktime(&tm); 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);
} }

75
t/t0006-date.sh Executable file
View File

@ -0,0 +1,75 @@
#!/bin/sh
test_description='test date parsing and printing'
. ./test-lib.sh
# arbitrary reference time: 2009-08-30 19:20:00
TEST_DATE_NOW=1251660000; export TEST_DATE_NOW
check_show() {
t=$(($TEST_DATE_NOW - $1))
echo "$t -> $2" >expect
test_expect_${3:-success} "relative date ($2)" "
test-date show $t >actual &&
test_cmp expect actual
"
}
check_show 5 '5 seconds ago'
check_show 300 '5 minutes ago'
check_show 18000 '5 hours ago'
check_show 432000 '5 days ago'
check_show 1728000 '3 weeks ago'
check_show 13000000 '5 months ago'
check_show 37500000 '1 year, 2 months ago'
check_show 55188000 '1 year, 9 months ago'
check_show 630000000 '20 years ago'
check_parse() {
echo "$1 -> $2" >expect
test_expect_${3:-success} "parse date ($1)" "
test-date parse '$1' >actual &&
test_cmp expect actual
"
}
check_parse 2008 bad
check_parse 2008-02 bad
check_parse 2008-02-14 bad
check_parse '2008-02-14 20:30:45' '2008-02-14 20:30:45 +0000'
check_approxidate() {
echo "$1 -> $2 +0000" >expect
test_expect_${3:-success} "parse approxidate ($1)" "
test-date approxidate '$1' >actual &&
test_cmp expect actual
"
}
check_approxidate now '2009-08-30 19:20:00'
check_approxidate '5 seconds ago' '2009-08-30 19:19:55'
check_approxidate 5.seconds.ago '2009-08-30 19:19:55'
check_approxidate 10.minutes.ago '2009-08-30 19:10:00'
check_approxidate yesterday '2009-08-29 19:20:00'
check_approxidate 3.days.ago '2009-08-27 19:20:00'
check_approxidate 3.weeks.ago '2009-08-09 19:20:00'
check_approxidate 3.months.ago '2009-05-30 19:20:00'
check_approxidate 2.years.3.months.ago '2007-05-30 19:20:00'
check_approxidate '6am yesterday' '2009-08-29 06:00:00'
check_approxidate '6pm yesterday' '2009-08-29 18:00:00'
check_approxidate '3:00' '2009-08-30 03:00:00'
check_approxidate '15:00' '2009-08-30 15:00:00'
check_approxidate 'noon today' '2009-08-30 12:00:00'
check_approxidate 'noon yesterday' '2009-08-29 12:00:00'
check_approxidate 'last tuesday' '2009-08-25 19:20:00'
check_approxidate 'July 5th' '2009-07-05 19:20:00'
check_approxidate '06/05/2009' '2009-06-05 19:20:00'
check_approxidate '06.05.2009' '2009-05-06 19:20:00'
check_approxidate 'Jun 6, 5AM' '2009-06-06 05:00:00'
check_approxidate '5AM Jun 6' '2009-06-06 05:00:00'
check_approxidate '6AM, June 7, 2009' '2009-06-07 06:00:00'
test_done

View File

@ -1,20 +1,67 @@
#include "cache.h" #include "cache.h"
int main(int argc, char **argv) static const char *usage_msg = "\n"
{ " test-date show [time_t]...\n"
int i; " test-date parse [date]...\n"
" test-date approxidate [date]...\n";
for (i = 1; i < argc; i++) { static void show_dates(char **argv, struct timeval *now)
{
char buf[128];
for (; *argv; argv++) {
time_t t = atoi(*argv);
show_date_relative(t, 0, now, buf, sizeof(buf));
printf("%s -> %s\n", *argv, buf);
}
}
static void parse_dates(char **argv, struct timeval *now)
{
for (; *argv; argv++) {
char result[100]; char result[100];
time_t t; time_t t;
memcpy(result, "bad", 4); result[0] = 0;
parse_date(argv[i], result, sizeof(result)); parse_date(*argv, result, sizeof(result));
t = strtoul(result, NULL, 0); t = strtoul(result, NULL, 0);
printf("%s -> %s -> %s", argv[i], result, ctime(&t)); printf("%s -> %s\n", *argv,
t ? show_date(t, 0, DATE_ISO8601) : "bad");
t = approxidate(argv[i]);
printf("%s -> %s\n", argv[i], ctime(&t));
} }
}
static void parse_approxidate(char **argv, struct timeval *now)
{
for (; *argv; argv++) {
time_t t;
t = approxidate_relative(*argv, now);
printf("%s -> %s\n", *argv, show_date(t, 0, DATE_ISO8601));
}
}
int main(int argc, char **argv)
{
struct timeval now;
const char *x;
x = getenv("TEST_DATE_NOW");
if (x) {
now.tv_sec = atoi(x);
now.tv_usec = 0;
}
else
gettimeofday(&now, NULL);
argv++;
if (!*argv)
usage(usage_msg);
if (!strcmp(*argv, "show"))
show_dates(argv+1, &now);
else if (!strcmp(*argv, "parse"))
parse_dates(argv+1, &now);
else if (!strcmp(*argv, "approxidate"))
parse_approxidate(argv+1, &now);
else
usage(usage_msg);
return 0; return 0;
} }