diff options
Diffstat (limited to 'date.c')
-rw-r--r-- | date.c | 262 |
1 files changed, 190 insertions, 72 deletions
@@ -86,107 +86,136 @@ static int local_tzoffset(unsigned long time) return offset * eastwest; } -const char *show_date_relative(unsigned long time, int tz, +void show_date_relative(unsigned long time, int tz, const struct timeval *now, - char *timebuf, - size_t timebuf_size) + struct strbuf *timebuf) { unsigned long diff; - if (now->tv_sec < time) - return "in the future"; + if (now->tv_sec < time) { + strbuf_addstr(timebuf, _("in the future")); + return; + } diff = now->tv_sec - time; if (diff < 90) { - snprintf(timebuf, timebuf_size, "%lu seconds ago", diff); - return timebuf; + strbuf_addf(timebuf, + Q_("%lu second ago", "%lu seconds ago", diff), diff); + return; } /* Turn it into minutes */ diff = (diff + 30) / 60; if (diff < 90) { - snprintf(timebuf, timebuf_size, "%lu minutes ago", diff); - return timebuf; + strbuf_addf(timebuf, + Q_("%lu minute ago", "%lu minutes ago", diff), diff); + return; } /* Turn it into hours */ diff = (diff + 30) / 60; if (diff < 36) { - snprintf(timebuf, timebuf_size, "%lu hours ago", diff); - return timebuf; + strbuf_addf(timebuf, + Q_("%lu hour ago", "%lu hours ago", diff), diff); + return; } /* 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; + strbuf_addf(timebuf, + Q_("%lu day ago", "%lu days ago", diff), diff); + return; } /* Say weeks for the past 10 weeks or so */ if (diff < 70) { - snprintf(timebuf, timebuf_size, "%lu weeks ago", (diff + 3) / 7); - return timebuf; + strbuf_addf(timebuf, + Q_("%lu week ago", "%lu weeks ago", (diff + 3) / 7), + (diff + 3) / 7); + return; } /* Say months for the past 12 months or so */ if (diff < 365) { - snprintf(timebuf, timebuf_size, "%lu months ago", (diff + 15) / 30); - return timebuf; + strbuf_addf(timebuf, + Q_("%lu month ago", "%lu months ago", (diff + 15) / 30), + (diff + 15) / 30); + return; } /* 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; + unsigned long totalmonths = (diff * 12 * 2 + 365) / (365 * 2); + unsigned long years = totalmonths / 12; + unsigned long months = totalmonths % 12; + if (months) { + struct strbuf sb = STRBUF_INIT; + strbuf_addf(&sb, Q_("%lu year", "%lu years", years), years); + strbuf_addf(timebuf, + /* TRANSLATORS: "%s" is "<n> years" */ + Q_("%s, %lu month ago", "%s, %lu months ago", months), + sb.buf, months); + strbuf_release(&sb); + } else + strbuf_addf(timebuf, + Q_("%lu year ago", "%lu years ago", years), years); + return; } /* Otherwise, just years. Centuries is probably overkill. */ - snprintf(timebuf, timebuf_size, "%lu years ago", (diff + 183) / 365); - return timebuf; + strbuf_addf(timebuf, + Q_("%lu year ago", "%lu years ago", (diff + 183) / 365), + (diff + 183) / 365); } const char *show_date(unsigned long time, int tz, enum date_mode mode) { struct tm *tm; - static char timebuf[200]; + static struct strbuf timebuf = STRBUF_INIT; if (mode == DATE_RAW) { - snprintf(timebuf, sizeof(timebuf), "%lu %+05d", time, tz); - return timebuf; + strbuf_reset(&timebuf); + strbuf_addf(&timebuf, "%lu %+05d", time, tz); + return timebuf.buf; } if (mode == DATE_RELATIVE) { struct timeval now; + + strbuf_reset(&timebuf); gettimeofday(&now, NULL); - return show_date_relative(time, tz, &now, - timebuf, sizeof(timebuf)); + show_date_relative(time, tz, &now, &timebuf); + return timebuf.buf; } if (mode == DATE_LOCAL) tz = local_tzoffset(time); tm = time_to_tm(time, tz); - if (!tm) - return NULL; + if (!tm) { + tm = time_to_tm(0, 0); + tz = 0; + } + + strbuf_reset(&timebuf); if (mode == DATE_SHORT) - sprintf(timebuf, "%04d-%02d-%02d", tm->tm_year + 1900, + strbuf_addf(&timebuf, "%04d-%02d-%02d", tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday); else if (mode == DATE_ISO8601) - sprintf(timebuf, "%04d-%02d-%02d %02d:%02d:%02d %+05d", + strbuf_addf(&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); - else if (mode == DATE_RFC2822) - sprintf(timebuf, "%.3s, %d %.3s %d %02d:%02d:%02d %+05d", + else if (mode == DATE_ISO8601_STRICT) { + char sign = (tz >= 0) ? '+' : '-'; + tz = abs(tz); + strbuf_addf(&timebuf, "%04d-%02d-%02dT%02d:%02d:%02d%c%02d:%02d", + tm->tm_year + 1900, + tm->tm_mon + 1, + tm->tm_mday, + tm->tm_hour, tm->tm_min, tm->tm_sec, + sign, tz / 100, tz % 100); + } else if (mode == DATE_RFC2822) + strbuf_addf(&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); else - sprintf(timebuf, "%.3s %.3s %d %02d:%02d:%02d %d%c%+05d", + strbuf_addf(&timebuf, "%.3s %.3s %d %02d:%02d:%02d %d%c%+05d", weekday_names[tm->tm_wday], month_names[tm->tm_mon], tm->tm_mday, @@ -194,7 +223,7 @@ const char *show_date(unsigned long time, int tz, enum date_mode mode) tm->tm_year + 1900, (mode == DATE_LOCAL) ? 0 : ' ', tz); - return timebuf; + return timebuf.buf; } /* @@ -365,7 +394,7 @@ static int is_date(int year, int month, int day, struct tm *now_tm, time_t now, * 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) + if ((specified != -1) && (now + 10*24*3600 < specified)) return 0; tm->tm_mon = r->tm_mon; tm->tm_mday = r->tm_mday; @@ -551,29 +580,41 @@ static int match_digit(const char *date, struct tm *tm, int *offset, int *tm_gmt static int match_tz(const char *date, int *offp) { char *end; - int offset = strtoul(date+1, &end, 10); - int min, hour; - int n = end - date - 1; + int hour = strtoul(date + 1, &end, 10); + int n = end - (date + 1); + int min = 0; - min = offset % 100; - hour = offset / 100; + if (n == 4) { + /* hhmm */ + min = hour % 100; + hour = hour / 100; + } else if (n != 2) { + min = 99; /* random crap */ + } else if (*end == ':') { + /* hh:mm? */ + min = strtoul(end + 1, &end, 10); + if (end - (date + 1) != 5) + min = 99; /* random crap */ + } /* otherwise we parsed "hh" */ /* - * 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. + * Don't accept any random crap. Even though some places have + * offset larger than 12 hours (e.g. Pacific/Kiritimati is at + * UTC+14), there is something wrong if hour part is much + * larger than that. We might also want to check that the + * minutes are divisible by 15 or something too. (Offset of + * Kathmandu, Nepal is UTC+5:45) */ - if (min < 60 && n > 2) { - offset = hour*60+min; + if (min < 60 && hour < 24) { + int offset = hour * 60 + min; if (*date == '-') offset = -offset; - *offp = offset; } return end - date; } -static int date_string(unsigned long date, int offset, char *buf, int len) +static void date_string(unsigned long date, int offset, struct strbuf *buf) { int sign = '+'; @@ -581,12 +622,39 @@ static int date_string(unsigned long date, int offset, char *buf, int len) offset = -offset; sign = '-'; } - return snprintf(buf, len, "%lu %c%02d%02d", date, sign, offset / 60, offset % 60); + strbuf_addf(buf, "%lu %c%02d%02d", date, sign, offset / 60, offset % 60); +} + +/* + * Parse a string like "0 +0000" as ancient timestamp near epoch, but + * only when it appears not as part of any other string. + */ +static int match_object_header_date(const char *date, unsigned long *timestamp, int *offset) +{ + char *end; + unsigned long stamp; + int ofs; + + if (*date < '0' || '9' < *date) + return -1; + stamp = strtoul(date, &end, 10); + if (*end != ' ' || stamp == ULONG_MAX || (end[1] != '+' && end[1] != '-')) + return -1; + date = end + 2; + ofs = strtol(date, &end, 10); + if ((*end != '\0' && (*end != '\n')) || end != date + 4) + return -1; + ofs = (ofs / 100) * 60 + (ofs % 100); + if (date[-1] == '-') + ofs = -ofs; + *timestamp = stamp; + *offset = ofs; + return 0; } /* 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. */ -int parse_date_toffset(const char *date, unsigned long *timestamp, int *offset) +int parse_date_basic(const char *date, unsigned long *timestamp, int *offset) { struct tm tm; int tm_gmt; @@ -609,6 +677,9 @@ int parse_date_toffset(const char *date, unsigned long *timestamp, int *offset) *offset = -1; tm_gmt = 0; + if (*date == '@' && + !match_object_header_date(date + 1, timestamp, offset)) + return 0; /* success */ for (;;) { int match = 0; unsigned char c = *date; @@ -634,25 +705,53 @@ int parse_date_toffset(const char *date, unsigned long *timestamp, int *offset) /* mktime uses local timezone */ *timestamp = tm_to_time_t(&tm); - if (*offset == -1) - *offset = (*timestamp - mktime(&tm)) / 60; + if (*offset == -1) { + time_t temp_time = mktime(&tm); + if ((time_t)*timestamp > temp_time) { + *offset = ((time_t)*timestamp - temp_time) / 60; + } else { + *offset = -(int)((temp_time - (time_t)*timestamp) / 60); + } + } if (*timestamp == -1) return -1; if (!tm_gmt) *timestamp -= *offset * 60; - return 1; /* success */ + return 0; /* success */ +} + +int parse_expiry_date(const char *date, unsigned long *timestamp) +{ + int errors = 0; + + if (!strcmp(date, "never") || !strcmp(date, "false")) + *timestamp = 0; + else if (!strcmp(date, "all") || !strcmp(date, "now")) + /* + * We take over "now" here, which usually translates + * to the current timestamp. This is because the user + * really means to expire everything she has done in + * the past, and by definition reflogs are the record + * of the past, and there is nothing from the future + * to be kept. + */ + *timestamp = ULONG_MAX; + else + *timestamp = approxidate_careful(date, &errors); + + return errors; } -int parse_date(const char *date, char *result, int maxlen) +int parse_date(const char *date, struct strbuf *result) { unsigned long timestamp; int offset; - if (parse_date_toffset(date, ×tamp, &offset) > 0) - return date_string(timestamp, offset, result, maxlen); - else + if (parse_date_basic(date, ×tamp, &offset)) return -1; + date_string(timestamp, offset, result); + return 0; } enum date_mode parse_date_format(const char *format) @@ -662,6 +761,9 @@ enum date_mode parse_date_format(const char *format) else if (!strcmp(format, "iso8601") || !strcmp(format, "iso")) return DATE_ISO8601; + else if (!strcmp(format, "iso8601-strict") || + !strcmp(format, "iso-strict")) + return DATE_ISO8601_STRICT; else if (!strcmp(format, "rfc2822") || !strcmp(format, "rfc")) return DATE_RFC2822; @@ -677,7 +779,7 @@ enum date_mode parse_date_format(const char *format) die("unknown date format %s", format); } -void datestamp(char *buf, int bufsize) +void datestamp(struct strbuf *out) { time_t now; int offset; @@ -687,7 +789,7 @@ void datestamp(char *buf, int bufsize) offset = tm_to_time_t(localtime(&now)) - now; offset /= 60; - date_string(now, offset, buf, bufsize); + date_string(now, offset, out); } /* @@ -820,7 +922,7 @@ static const char *approxidate_alpha(const char *date, struct tm *tm, struct tm const char *end = date; int i; - while (isalpha(*++end)); + while (isalpha(*++end)) ; for (i = 0; i < 12; i++) { @@ -1004,9 +1106,8 @@ unsigned long approxidate_relative(const char *date, const struct timeval *tv) int offset; int errors = 0; - if (parse_date_toffset(date, ×tamp, &offset) > 0) + if (!parse_date_basic(date, ×tamp, &offset)) return timestamp; - return approxidate_str(date, tv, &errors); } @@ -1019,7 +1120,7 @@ unsigned long approxidate_careful(const char *date, int *error_ret) if (!error_ret) error_ret = &dummy; - if (parse_date_toffset(date, ×tamp, &offset) > 0) { + if (!parse_date_basic(date, ×tamp, &offset)) { *error_ret = 0; return timestamp; } @@ -1027,3 +1128,20 @@ unsigned long approxidate_careful(const char *date, int *error_ret) gettimeofday(&tv, NULL); return approxidate_str(date, &tv, error_ret); } + +int date_overflows(unsigned long t) +{ + time_t sys; + + /* If we overflowed our unsigned long, that's bad... */ + if (t == ULONG_MAX) + return 1; + + /* + * ...but we also are going to feed the result to system + * functions that expect time_t, which is often "signed long". + * Make sure that we fit into time_t, as well. + */ + sys = t; + return t != sys || (t < 1) != (sys < 1); +} |