cGit-UI for Git Repositories

cGit-UI – is a web interface for Git Repositories. cGit CGI script is writen in C and therefore it's fast enough

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