5 kx
5 kx #ifdef HAVE_CONFIG_H
5 kx #include <config.h>
5 kx #endif
5 kx
5 kx #include <stdlib.h>
5 kx #include <stdio.h>
5 kx #include <sys/sysinfo.h>
5 kx #include <sys/types.h>
5 kx #ifdef HAVE_INTTYPES_H
5 kx #include <inttypes.h>
5 kx #else
5 kx #include <stdint.h>
5 kx #endif
5 kx #include <stddef.h> /* offsetof(3) */
5 kx #include <dirent.h>
5 kx #include <sys/stat.h> /* chmod(2) */
5 kx #include <sys/file.h>
5 kx #include <sys/mman.h>
5 kx #include <fcntl.h>
5 kx #include <limits.h>
5 kx #include <string.h> /* strdup(3) */
5 kx #include <libgen.h> /* basename(3) */
5 kx #include <ctype.h> /* tolower(3) */
5 kx #include <errno.h>
5 kx #include <time.h>
5 kx #include <sys/time.h>
5 kx #include <pwd.h>
5 kx #include <grp.h>
5 kx #include <stdarg.h>
5 kx #include <unistd.h>
5 kx
5 kx #include <nls.h>
5 kx
5 kx #include <defs.h>
5 kx
5 kx #include <strbuf.h>
5 kx #include <date.h>
5 kx
5 kx
5 kx /* Valid rule actions */
5 kx enum rule_action
5 kx {
5 kx ACCUM, /* Accumulate a decimal value */
5 kx MICRO, /* Accumulate microseconds */
5 kx TZIND, /* Handle +, -, Z */
5 kx NOOP, /* Do nothing */
5 kx SKIPFROM, /* If at end-of-value, accept the match. Otherwise,
5 kx if the next template character matches the current
5 kx value character, continue processing as normal.
5 kx Otherwise, attempt to complete matching starting
5 kx immediately after the first subsequent occurrance of
5 kx ']' in the template. */
5 kx SKIP, /* Ignore this template character */
5 kx ACCEPT /* Accept the value */
5 kx };
5 kx
5 kx /* How to handle a particular character in a template */
5 kx struct rule
5 kx {
5 kx char key; /* The template char that this rule matches */
5 kx const char *valid; /* String of valid chars for this rule */
5 kx enum rule_action action; /* What action to take when the rule is matched */
5 kx int offset; /* Where to store the any results of the action,
5 kx expressed in terms of bytes relative to the
5 kx base of a match_state object. */
5 kx };
5 kx
5 kx struct match_state
5 kx {
5 kx struct tm base;
5 kx struct timeval tv;
5 kx int gmtoff;
5 kx int gmtoff_hours;
5 kx int gmtoff_minutes;
5 kx };
5 kx
5 kx
5 kx #define DIGITS "0123456789"
5 kx
5 kx /*
5 kx A declarative specification of how each template character
5 kx should be processed, using a rule for each valid symbol.
5 kx */
5 kx static const struct rule rules[] =
5 kx {
5 kx { 'Y', DIGITS, ACCUM, offsetof(struct match_state, base.tm_year) },
5 kx { 'M', DIGITS, ACCUM, offsetof(struct match_state, base.tm_mon) },
5 kx { 'D', DIGITS, ACCUM, offsetof(struct match_state, base.tm_mday) },
5 kx { 'h', DIGITS, ACCUM, offsetof(struct match_state, base.tm_hour) },
5 kx { 'm', DIGITS, ACCUM, offsetof(struct match_state, base.tm_min) },
5 kx { 's', DIGITS, ACCUM, offsetof(struct match_state, base.tm_sec) },
5 kx { 'u', DIGITS, MICRO, offsetof(struct match_state, tv.tv_usec) },
5 kx { 'O', DIGITS, ACCUM, offsetof(struct match_state, gmtoff_hours) },
5 kx { 'o', DIGITS, ACCUM, offsetof(struct match_state, gmtoff_minutes) },
5 kx { '+', "-+", TZIND, 0 },
5 kx { 'Z', "Z", TZIND, 0 },
5 kx { ':', ":", NOOP, 0 },
5 kx { '-', "-", NOOP, 0 },
5 kx { 'T', "T", NOOP, 0 },
5 kx { ' ', " ", NOOP, 0 },
5 kx { '.', ".,", NOOP, 0 },
5 kx { '[', NULL, SKIPFROM, 0 },
5 kx { ']', NULL, SKIP, 0 },
5 kx { '\0', NULL, ACCEPT, 0 },
5 kx };
5 kx
5 kx /* Return the rule associated with TCHAR, or NULL if there is no such rule. */
5 kx static const struct rule *find_rule( char tchar )
5 kx {
5 kx int i = sizeof(rules)/sizeof(rules[0]);
5 kx while( i-- )
5 kx if( rules[i].key == tchar )
5 kx return &rules[i];
5 kx return NULL;
5 kx }
5 kx
5 kx /*
5 kx Attempt to match the date-string in VALUE to the provided TEMPLATE,
5 kx using the rules defined above. Return TRUE on successful match,
5 kx FALSE otherwise. On successful match, fill in *TM with the
5 kx matched values and set *LOCALTZ to GMT-offset if the local time zone
5 kx should be used to interpret the match.
5 kx */
5 kx static int template_match( struct tm *tm, int *localtz, const char *template, const char *value )
5 kx {
5 kx int multiplier = 100000;
5 kx int tzind = 0;
5 kx struct match_state ms;
5 kx char *base = (char *)&ms;
5 kx
5 kx memset( &ms, 0, sizeof(ms) );
5 kx
5 kx for( ;; )
5 kx {
5 kx const struct rule *match = find_rule(*template++);
5 kx char vchar = *value++;
5 kx int *place;
5 kx
5 kx if( !match || (match->valid && (!vchar || !strchr(match->valid, vchar))) )
5 kx return FALSE;
5 kx
5 kx /* Compute the address of memory location affected by this
5 kx rule by adding match->offset bytes to the address of ms.
5 kx Because this is a byte-quantity, it is necessary to cast
5 kx &ms to char *. */
5 kx place = (int *)(base + match->offset);
5 kx switch( match->action )
5 kx {
5 kx case ACCUM:
5 kx *place = *place * 10 + vchar - '0';
5 kx continue;
5 kx case MICRO:
5 kx *place += (vchar - '0') * multiplier;
5 kx multiplier /= 10;
5 kx continue;
5 kx case TZIND:
5 kx tzind = vchar;
5 kx continue;
5 kx case SKIP:
5 kx value--;
5 kx continue;
5 kx case NOOP:
5 kx continue;
5 kx case SKIPFROM:
5 kx if( !vchar )
5 kx break;
5 kx match = find_rule(*template);
5 kx if (!strchr(match->valid, vchar))
5 kx template = strchr(template, ']') + 1;
5 kx value--;
5 kx continue;
5 kx case ACCEPT:
5 kx if( vchar )
5 kx return FALSE;
5 kx break;
5 kx }
5 kx
5 kx break;
5 kx }
5 kx
5 kx /* Validate gmt offset here, since we can't reliably do it later. */
5 kx if( ms.gmtoff_hours > 23 || ms.gmtoff_minutes > 59 )
5 kx return FALSE;
5 kx
5 kx /*
5 kx tzind will be '+' or '-' for an explicit time zone,
5 kx 'Z' to indicate UTC, or 0 to indicate local time.
5 kx */
5 kx switch( tzind )
5 kx {
5 kx case '+':
5 kx ms.gmtoff = ms.gmtoff_hours * 3600 + ms.gmtoff_minutes * 60;
5 kx break;
5 kx case '-':
5 kx ms.gmtoff = -(ms.gmtoff_hours * 3600 + ms.gmtoff_minutes * 60);
5 kx break;
5 kx }
5 kx
5 kx *tm = ms.base;
5 kx *localtz = ms.gmtoff;
5 kx return TRUE;
5 kx }
5 kx
5 kx static int valid_days_by_month[] =
5 kx {
5 kx 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
5 kx };
5 kx
5 kx /*
5 kx Returns -1 on error,
5 kx time_t as the number of seconds since Epoch, 1970-01-01 00:00:00 +0000 (UTC)
5 kx on success.
5 kx */
5 kx time_t parse_date( struct tm *tm, const char *text )
5 kx {
5 kx time_t n, ret = (time_t)-1;
5 kx struct tm pt, *now;
5 kx int localtz;
5 kx
5 kx n = time( NULL ); /* current UTC time */
5 kx now = gmtime( &n );
5 kx
5 kx
5 kx if( /* ISO-8601 extended, date only: */
5 kx template_match( &pt, &localtz, "YYYY-M[M]-D[D]", text ) ||
5 kx /* ISO-8601 extended, UTC: */
5 kx template_match( &pt, &localtz, "YYYY-M[M]-D[D]Th[h]:mm[:ss[.u[u[u[u[u[u][Z]", text ) ||
5 kx /* ISO-8601 extended, with offset: */
5 kx template_match( &pt, &localtz, "YYYY-M[M]-D[D]Th[h]:mm[:ss[.u[u[u[u[u[u]+OO[:oo]", text ) ||
5 kx /* ISO-8601 basic, date only */
5 kx template_match( &pt, &localtz, "YYYYMMDD", text ) ||
5 kx /* ISO-8601 basic, UTC: */
5 kx template_match( &pt, &localtz, "YYYYMMDDThhmm[ss[.u[u[u[u[u[u][Z]", text ) ||
5 kx /* ISO-8601 basic, with offset: */
5 kx template_match( &pt, &localtz, "YYYYMMDDThhmm[ss[.u[u[u[u[u[u]+OO[oo]", text ) ||
5 kx /* "git log" format: */
5 kx template_match( &pt, &localtz, "YYYY-M[M]-D[D] h[h]:mm[:ss[.u[u[u[u[u[u][ +OO[oo]", text ) ||
5 kx /* GNU date's iso-8601: */
5 kx template_match( &pt, &localtz, "YYYY-M[M]-D[D]Th[h]:mm[:ss[.u[u[u[u[u[u]+OO[oo]", text ) )
5 kx {
5 kx pt.tm_year -= 1900;
5 kx pt.tm_mon -= 1;
5 kx }
5 kx else if( template_match( &pt, &localtz, "h[h]:mm[:ss[.u[u[u[u[u[u]", text) ) /* Just a time */
5 kx {
5 kx pt.tm_year = now->tm_year;
5 kx pt.tm_mon = now->tm_mon;
5 kx pt.tm_mday = now->tm_mday;
5 kx }
5 kx
5 kx /* Range validation, allowing for leap seconds */
5 kx if( pt.tm_mon < 0 ||
5 kx pt.tm_mon > 11 ||
5 kx pt.tm_mday > valid_days_by_month[pt.tm_mon] ||
5 kx pt.tm_mday < 1 ||
5 kx pt.tm_hour > 23 ||
5 kx pt.tm_min > 59 ||
5 kx pt.tm_sec > 60 )
5 kx return ret;
5 kx
5 kx /*
5 kx february/leap-year day checking. tm_year is bias-1900, so
5 kx centuries that equal 100 (mod 400) are multiples of 400.
5 kx */
5 kx if( pt.tm_mon == 1 &&
5 kx pt.tm_mday == 29 &&
5 kx (pt.tm_year % 4 != 0 || (pt.tm_year % 100 == 0 && pt.tm_year % 400 != 100)) )
5 kx return ret;
5 kx
5 kx if( localtz )
5 kx {
5 kx struct tm *gmt = NULL;
5 kx time_t time;
5 kx
5 kx time = mktime( &pt ); /* brocken-down tm asumed as localtime */
5 kx if( time == -1 )
5 kx return ret;
5 kx
5 kx time -= (time_t)localtz;
5 kx
5 kx gmt = localtime( &time );
5 kx if( !gmt )
5 kx return ret;
5 kx
5 kx memcpy( (void *)&pt, (const void *)gmt, sizeof(struct tm) );
5 kx }
5 kx
5 kx memcpy( (void *)tm, (const void *)&pt, sizeof(struct tm) );
5 kx
5 kx return mktime( &pt );
5 kx }
5 kx
5 kx
5 kx void show_date_relative( struct strbuf *sb, time_t t )
5 kx {
5 kx time_t now, diff;
5 kx
5 kx if( !sb || !t ) return;
5 kx
5 kx now = time( NULL );
5 kx if( now < t )
5 kx {
5 kx strbuf_addstr( sb, _("in the future") );
5 kx return;
5 kx }
5 kx diff = now - t;
5 kx if( diff < 90 )
5 kx {
5 kx strbuf_addf( sb, Q_("%"PRIdMAX" second ago", "%"PRIdMAX" seconds ago", diff), diff );
5 kx return;
5 kx }
5 kx /* Turn it into minutes */
5 kx diff = (diff + 30) / 60;
5 kx if( diff < 90 )
5 kx {
5 kx strbuf_addf( sb, Q_("%"PRIdMAX" minute ago", "%"PRIdMAX" minutes ago", diff), diff );
5 kx return;
5 kx }
5 kx /* Turn it into hours */
5 kx diff = (diff + 30) / 60;
5 kx if( diff < 36 )
5 kx {
5 kx strbuf_addf( sb, Q_("%"PRIdMAX" hour ago", "%"PRIdMAX" hours ago", diff), diff );
5 kx return;
5 kx }
5 kx /* We deal with number of days from here on */
5 kx diff = (diff + 12) / 24;
5 kx if( diff < 14 )
5 kx {
5 kx strbuf_addf( sb, Q_("%"PRIdMAX" day ago", "%"PRIdMAX" days ago", diff), diff );
5 kx return;
5 kx }
5 kx /* Say weeks for the past 10 weeks or so */
5 kx if( diff < 70 )
5 kx {
5 kx strbuf_addf( sb, Q_("%"PRIdMAX" week ago", "%"PRIdMAX" weeks ago", (diff+3)/7), (diff+3)/7 );
5 kx return;
5 kx }
5 kx /* Say months for the past 12 months or so */
5 kx if( diff < 365 )
5 kx {
5 kx strbuf_addf( sb, Q_("%"PRIdMAX" month ago", "%"PRIdMAX" months ago", (diff+15)/30), (diff+15)/30 );
5 kx return;
5 kx }
5 kx /* Give years and months for 5 years or so */
5 kx if( diff < 1825 )
5 kx {
5 kx time_t totalmonths = (diff * 12 * 2 + 365) / (365 * 2);
5 kx time_t years = totalmonths / 12;
5 kx time_t months = totalmonths % 12;
5 kx if( months )
5 kx {
5 kx struct strbuf buf = STRBUF_INIT;
5 kx strbuf_addf( &buf, Q_("%"PRIdMAX" year", "%"PRIdMAX" years", years), years );
5 kx strbuf_addf( sb,
5 kx /* TRANSLATORS: "%s" is "<n> months" */
5 kx Q_("%s, %"PRIdMAX" month ago", "%s, %"PRIdMAX" months ago", months), buf.buf, months );
5 kx strbuf_release( &buf );
5 kx }
5 kx else
5 kx strbuf_addf( sb, Q_("%"PRIdMAX" year ago", "%"PRIdMAX" years ago", years), years );
5 kx return;
5 kx }
5 kx /* Otherwise, just years. Centuries is probably overkill. */
5 kx strbuf_addf( sb, Q_("%"PRIdMAX" year ago", "%"PRIdMAX" years ago", (diff+183)/365), (diff+183)/365 );
5 kx }
5 kx
5 kx struct date_mode *date_mode_from_type( enum date_mode_type type )
5 kx {
5 kx static struct date_mode mode;
5 kx mode.type = type;
5 kx mode.local = 0;
5 kx return &mode;
5 kx }
5 kx
5 kx static const char *month_names[] = {
5 kx "January", "February", "March", "April", "May", "June",
5 kx "July", "August", "September", "October", "November", "December"
5 kx };
5 kx
5 kx static const char *weekday_names[] = {
5 kx "Sundays", "Mondays", "Tuesdays", "Wednesdays", "Thursdays", "Fridays", "Saturdays"
5 kx };
5 kx
5 kx static time_t gm_time_t( time_t time, int tz )
5 kx {
5 kx int minutes;
5 kx
5 kx minutes = tz < 0 ? -tz : tz;
5 kx minutes = (minutes / 100)*60 + (minutes % 100);
5 kx minutes = tz < 0 ? -minutes : minutes;
5 kx
5 kx time += minutes * 60;
5 kx
5 kx return time;
5 kx }
5 kx
5 kx static struct tm *time_to_tm( time_t time, int tz, struct tm *tm )
5 kx {
5 kx time_t t = gm_time_t( time, tz );
5 kx return gmtime_r( &t, tm );
5 kx }
5 kx
5 kx static struct tm *time_to_tm_local( time_t time, struct tm *tm )
5 kx {
5 kx time_t t = time;
5 kx return localtime_r( &t, tm );
5 kx }
5 kx
5 kx /**********************************************************
5 kx Fill in the localtime 'struct tm' for the supplied time,
5 kx and return the local tz.
5 kx */
5 kx static int local_time_tzoffset( time_t t, struct tm *tm )
5 kx {
5 kx time_t t_local;
5 kx int offset, eastwest;
5 kx
5 kx localtime_r( &t, tm );
5 kx t_local = mktime( tm );
5 kx if( t_local == -1 )
5 kx return 0; /* error; just use +0000 */
5 kx if( t_local < t )
5 kx {
5 kx eastwest = -1;
5 kx offset = t - t_local;
5 kx }
5 kx else
5 kx {
5 kx eastwest = 1;
5 kx offset = t_local - t;
5 kx }
5 kx offset /= 60; /* in minutes */
5 kx offset = (offset % 60) + ((offset / 60) * 100);
5 kx return offset * eastwest;
5 kx }
5 kx
5 kx static int local_tzoffset( time_t time )
5 kx {
5 kx struct tm tm;
5 kx return local_time_tzoffset( time, &tm );
5 kx }
5 kx
5 kx
5 kx static void show_date_normal( struct strbuf *sb,
5 kx time_t time, struct tm *tm, int tz,
5 kx struct tm *human_tm, int human_tz, int local )
5 kx {
5 kx struct
5 kx {
5 kx unsigned int year:1,
5 kx date:1,
5 kx wday:1,
5 kx time:1,
5 kx seconds:1,
5 kx tz:1;
5 kx } hide = { 0 };
5 kx
5 kx hide.tz = local || tz == human_tz;
5 kx hide.year = tm->tm_year == human_tm->tm_year;
5 kx if( hide.year )
5 kx {
5 kx if( tm->tm_mon == human_tm->tm_mon )
5 kx {
5 kx if( tm->tm_mday > human_tm->tm_mday )
5 kx {
5 kx /* Future date: think timezones */
5 kx }
5 kx else if( tm->tm_mday == human_tm->tm_mday )
5 kx {
5 kx hide.date = hide.wday = 1;
5 kx }
5 kx else if( tm->tm_mday + 5 > human_tm->tm_mday )
5 kx {
5 kx /* Leave just weekday if it was a few days ago */
5 kx hide.date = 1;
5 kx }
5 kx }
5 kx }
5 kx
5 kx /* Show "today" times as just relative times */
5 kx if( hide.wday )
5 kx {
5 kx show_date_relative( sb, time );
5 kx return;
5 kx }
5 kx
5 kx /******************************************************
5 kx Always hide seconds for human-readable.
5 kx Hide timezone if showing date.
5 kx Hide weekday and time if showing year.
5 kx
5 kx The logic here is two-fold:
5 kx (a) only show details when recent enough to matter
5 kx (b) keep the maximum length "similar", and in check
5 kx ******************************************************/
5 kx if( human_tm->tm_year )
5 kx {
5 kx hide.seconds = 1;
5 kx hide.tz |= !hide.date;
5 kx hide.wday = hide.time = !hide.year;
5 kx }
5 kx
5 kx if( !hide.wday )
5 kx strbuf_addf( sb, "%.3s ", weekday_names[tm->tm_wday] );
5 kx if( !hide.date )
5 kx strbuf_addf( sb, "%.3s %d ", month_names[tm->tm_mon], tm->tm_mday );
5 kx
5 kx /* Do we want AM/PM depending on locale? */
5 kx if( !hide.time )
5 kx {
5 kx strbuf_addf( sb, "%02d:%02d", tm->tm_hour, tm->tm_min );
5 kx if( !hide.seconds )
5 kx strbuf_addf( sb, ":%02d", tm->tm_sec );
5 kx }
5 kx else
5 kx strbuf_rtrim( sb );
5 kx
5 kx if( !hide.year )
5 kx strbuf_addf( sb, " %d", tm->tm_year + 1900 );
5 kx
5 kx if( !hide.tz )
5 kx strbuf_addf( sb, " %+05d", tz );
5 kx }
5 kx
5 kx void show_date( struct strbuf *sb, time_t t, int tz, const struct date_mode *mode )
5 kx {
5 kx struct tm *tm;
5 kx struct tm tmbuf = { 0 };
5 kx struct tm human_tm = { 0 };
5 kx int human_tz = -1;
5 kx
5 kx if( mode->type == DATE_UNIX )
5 kx {
5 kx strbuf_addf( sb, "%"PRIdMAX, t );
5 kx }
5 kx
5 kx if( mode->type == DATE_HUMAN )
5 kx {
5 kx time_t now = time( NULL );
5 kx
5 kx /* Fill in the data for "current time" in human_tz and human_tm */
5 kx human_tz = local_time_tzoffset( now, &human_tm );
5 kx }
5 kx
5 kx if( mode->local )
5 kx tz = local_tzoffset( t );
5 kx
5 kx if( mode->type == DATE_RAW )
5 kx {
5 kx strbuf_addf( sb, "%"PRIdMAX" %+05d", t, tz );
5 kx }
5 kx
5 kx if( mode->type == DATE_RELATIVE )
5 kx {
5 kx show_date_relative( sb, t );
5 kx }
5 kx
5 kx if( mode->local )
5 kx tm = time_to_tm_local( t, &tmbuf );
5 kx else
5 kx tm = time_to_tm( t, tz, &tmbuf );
5 kx if (!tm) {
5 kx tm = time_to_tm( 0, 0, &tmbuf );
5 kx tz = 0;
5 kx }
5 kx
5 kx if( mode->type == DATE_SHORT )
5 kx strbuf_addf( sb, "%04d-%02d-%02d", tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday );
5 kx else if( mode->type == DATE_ISO8601 )
5 kx strbuf_addf( sb, "%04d-%02d-%02d %02d:%02d:%02d %+05d",
5 kx tm->tm_year + 1900,
5 kx tm->tm_mon + 1,
5 kx tm->tm_mday,
5 kx tm->tm_hour, tm->tm_min, tm->tm_sec,
5 kx tz );
5 kx else if( mode->type == DATE_ISO8601_STRICT )
5 kx {
5 kx char sign = (tz >= 0) ? '+' : '-';
5 kx tz = abs( tz );
5 kx strbuf_addf( sb, "%04d-%02d-%02dT%02d:%02d:%02d%c%02d:%02d",
5 kx tm->tm_year + 1900,
5 kx tm->tm_mon + 1,
5 kx tm->tm_mday,
5 kx tm->tm_hour, tm->tm_min, tm->tm_sec,
5 kx sign, tz / 100, tz % 100 );
5 kx }
5 kx else if( mode->type == DATE_RFC2822 )
5 kx strbuf_addf( sb, "%.3s, %d %.3s %d %02d:%02d:%02d %+05d",
5 kx weekday_names[tm->tm_wday], tm->tm_mday,
5 kx month_names[tm->tm_mon], tm->tm_year + 1900,
5 kx tm->tm_hour, tm->tm_min, tm->tm_sec, tz );
5 kx else
5 kx show_date_normal( sb, t, tm, tz, &human_tm, human_tz, mode->local );
5 kx }