* Copyright (C) Linus Torvalds, 2005
*/
-#include <time.h>
-#include <sys/time.h>
-
#include "cache.h"
static time_t my_mktime(struct tm *tm)
"Sundays", "Mondays", "Tuesdays", "Wednesdays", "Thursdays", "Fridays", "Saturdays"
};
+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;
+}
+
/*
* 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,
*/
static struct tm *time_to_tm(unsigned long time, int tz)
{
- time_t t;
- int minutes;
-
- minutes = tz < 0 ? -tz : tz;
- minutes = (minutes / 100)*60 + (minutes % 100);
- minutes = tz < 0 ? -minutes : minutes;
- t = time + minutes * 60;
+ time_t t = gm_time_t(time, tz);
return gmtime(&t);
}
-const char *show_date(unsigned long time, int tz)
+/*
+ * What value of "tz" was in effect back then at "time" in the
+ * local timezone?
+ */
+static int local_tzoffset(unsigned long time)
{
- struct tm *tm;
- static char timebuf[200];
-
- tm = time_to_tm(time, tz);
- if (!tm)
- return NULL;
- sprintf(timebuf, "%.3s %.3s %d %02d:%02d:%02d %d %+05d",
- weekday_names[tm->tm_wday],
- month_names[tm->tm_mon],
- tm->tm_mday,
- tm->tm_hour, tm->tm_min, tm->tm_sec,
- tm->tm_year + 1900, tz);
- return timebuf;
+ time_t t, t_local;
+ struct tm tm;
+ int offset, eastwest;
+
+ t = time;
+ localtime_r(&t, &tm);
+ t_local = my_mktime(&tm);
+
+ 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;
}
-const char *show_rfc2822_date(unsigned long time, int tz)
+const char *show_date(unsigned long time, int tz, enum date_mode mode)
{
struct tm *tm;
static char timebuf[200];
+ if (mode == DATE_RELATIVE) {
+ unsigned long diff;
+ struct timeval now;
+ gettimeofday(&now, NULL);
+ if (now.tv_sec < time)
+ return "in the future";
+ 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;
+ }
+ /* Else fall back on absolute format.. */
+ }
+
+ if (mode == DATE_LOCAL)
+ tz = local_tzoffset(time);
+
tm = time_to_tm(time, tz);
if (!tm)
return NULL;
- 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);
+ if (mode == DATE_SHORT)
+ sprintf(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",
+ 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",
+ 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",
+ weekday_names[tm->tm_wday],
+ month_names[tm->tm_mon],
+ tm->tm_mday,
+ tm->tm_hour, tm->tm_min, tm->tm_sec,
+ tm->tm_year + 1900,
+ (mode == DATE_LOCAL) ? 0 : ' ',
+ tz);
return timebuf;
}
}
if (match_string(date, "PM") == 2) {
- if (tm->tm_hour > 0 && tm->tm_hour < 12)
- tm->tm_hour += 12;
+ tm->tm_hour = (tm->tm_hour % 12) + 12;
+ return 2;
+ }
+
+ if (match_string(date, "AM") == 2) {
+ tm->tm_hour = (tm->tm_hour % 12) + 0;
return 2;
}
}
/*
- * We've seen a digit. Time? Year? Date?
+ * We've seen a digit. Time? Year? Date?
*/
static int match_digit(const char *date, struct tm *tm, int *offset, int *tm_gmt)
{
num = strtoul(date, &end, 10);
/*
- * Seconds since 1970? We trigger on that for anything after Jan 1, 2000
+ * 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.
*/
- if (num > 946684800) {
+ if (num >= 100000000) {
time_t time = num;
if (gmtime_r(&time, tm)) {
*tm_gmt = 1;
} else if (num > 0 && num < 13) {
tm->tm_mon = num-1;
}
-
+
return n;
}
if (!match) {
/* BAD CRAP */
match = 1;
- }
+ }
date += match;
}
/* mktime uses local timezone */
- then = my_mktime(&tm);
+ then = my_mktime(&tm);
if (offset == -1)
offset = (then - mktime(&tm)) / 60;
return date_string(then, offset, result, maxlen);
}
+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);
+}
+
void datestamp(char *buf, int bufsize)
{
time_t now;
date_time(tm, 17);
}
+static void date_pm(struct tm *tm, int *num)
+{
+ int hour, n = *num;
+ *num = 0;
+
+ hour = tm->tm_hour;
+ if (n) {
+ hour = n;
+ tm->tm_min = 0;
+ tm->tm_sec = 0;
+ }
+ tm->tm_hour = (hour % 12) + 12;
+}
+
+static void date_am(struct tm *tm, int *num)
+{
+ int hour, n = *num;
+ *num = 0;
+
+ hour = tm->tm_hour;
+ if (n) {
+ hour = n;
+ tm->tm_min = 0;
+ tm->tm_sec = 0;
+ }
+ tm->tm_hour = (hour % 12);
+}
+
+static void date_never(struct tm *tm, int *num)
+{
+ tm->tm_mon = tm->tm_wday = tm->tm_yday
+ = tm->tm_hour = tm->tm_min = tm->tm_sec = 0;
+ tm->tm_year = 70;
+ tm->tm_mday = 1;
+}
+
static const struct special {
const char *name;
void (*fn)(struct tm *, int *);
{ "noon", date_noon },
{ "midnight", date_midnight },
{ "tea", date_tea },
+ { "PM", date_pm },
+ { "AM", date_am },
+ { "never", date_never },
{ NULL }
};
{ "days", 24*60*60 },
{ "weeks", 7*24*60*60 },
{ NULL }
-};
+};
static const char *approxidate_alpha(const char *date, struct tm *tm, int *num)
{
const struct typelen *tl;
const struct special *s;
const char *end = date;
- int n = 1, i;
+ int i;
- while (isalpha(*++end))
- n++;
+ while (isalpha(*++end));
+ ;
for (i = 0; i < 12; i++) {
int match = match_string(date, month_names[i]);
return end;
}
+static const char *approxidate_digit(const char *date, struct tm *tm, int *num)
+{
+ char *end;
+ unsigned long number = strtoul(date, &end, 10);
+
+ 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;
+ }
+ }
+
+ *num = number;
+ return end;
+}
+
unsigned long approxidate(const char *date)
{
int number = 0;
break;
date++;
if (isdigit(c)) {
- char *end;
- number = strtoul(date-1, &end, 10);
- date = end;
+ date = approxidate_digit(date-1, &tm, &number);
continue;
}
if (isalpha(c))