/*
    GSK - a library to write servers
    Copyright (C) 1999-2000 Dave Benson

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public
    License as published by the Free Software Foundation; either
    version 2 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public
    License along with this library; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA

    Contact:
        daveb@ffem.org <Dave Benson>
*/

#include "gskdate.h"
#include "gsktimegm.h"
#include <ctype.h>
#include <stdlib.h>

#if 1
#define DEBUG_DATE(x)    g_message x
#else
#define DEBUG_DATE(x)
#endif

#define USE_LOCALTIME_HACK      0


static gboolean inited = FALSE;
static GHashTable *day_of_week_from_name = NULL;
static GHashTable *month_from_name = NULL;
static GHashTable *time_offset_from_name = NULL;

static void add_name3_to_index(GHashTable *table,
                               const char *lc_name3,
			       int         index)
{
  const guint8 *name = (const guint8 *) lc_name3;
  int iname = (((int)name[0]) << 0)
            | (((int)name[1]) << 8)
            | (((int)name[2]) << 16);
  g_hash_table_insert (table, GINT_TO_POINTER (iname), GINT_TO_POINTER (index));
}

static void init_tables ()
{
  if (inited)
    return;
  inited = TRUE;
  day_of_week_from_name = g_hash_table_new (NULL, NULL);
  add_name3_to_index (day_of_week_from_name, "sun", 1);
  add_name3_to_index (day_of_week_from_name, "mon", 2);
  add_name3_to_index (day_of_week_from_name, "tue", 3);
  add_name3_to_index (day_of_week_from_name, "wed", 4);
  add_name3_to_index (day_of_week_from_name, "thu", 5);
  add_name3_to_index (day_of_week_from_name, "fri", 6);
  add_name3_to_index (day_of_week_from_name, "sat", 7);

  month_from_name = g_hash_table_new (NULL, NULL);
  add_name3_to_index (month_from_name, "jan", 1);
  add_name3_to_index (month_from_name, "feb", 2);
  add_name3_to_index (month_from_name, "mar", 3);
  add_name3_to_index (month_from_name, "apr", 4);
  add_name3_to_index (month_from_name, "may", 5);
  add_name3_to_index (month_from_name, "jun", 6);
  add_name3_to_index (month_from_name, "jul", 7);
  add_name3_to_index (month_from_name, "aug", 8);
  add_name3_to_index (month_from_name, "sep", 9);
  add_name3_to_index (month_from_name, "oct", 10);
  add_name3_to_index (month_from_name, "nov", 11);
  add_name3_to_index (month_from_name, "dec", 12);

  time_offset_from_name = g_hash_table_new (NULL, NULL);
  add_name3_to_index (time_offset_from_name, "gmt", 0);
  add_name3_to_index (time_offset_from_name, "pst", -8 * 60);
  add_name3_to_index (time_offset_from_name, "pdt", -7 * 60);
  add_name3_to_index (time_offset_from_name, "mst", -7 * 60);
  add_name3_to_index (time_offset_from_name, "mdt", -6 * 60);
  add_name3_to_index (time_offset_from_name, "cst", -6 * 60);
  add_name3_to_index (time_offset_from_name, "cdt", -5 * 60);
  add_name3_to_index (time_offset_from_name, "est", -5 * 60);
  add_name3_to_index (time_offset_from_name, "edt", -4 * 60);
}



static int get_day_of_week (const char *name)
{
  int i;
  i = (guint8) (tolower (name[0]));
  i |= (int) ((guint8) (tolower (name[1]))) << 8;
  i |= (int) ((guint8) (tolower (name[2]))) << 16;
  return GPOINTER_TO_INT (g_hash_table_lookup (day_of_week_from_name,
                                               GINT_TO_POINTER (i)));
}

static int get_month (const char *name)
{
  int i;
  i = (guint8) (tolower (name[0]));
  i |= (int) ((guint8) (tolower (name[1]))) << 8;
  i |= (int) ((guint8) (tolower (name[2]))) << 16;
  return GPOINTER_TO_INT (g_hash_table_lookup (month_from_name,
                                               GINT_TO_POINTER (i)));
}

static gboolean parse_military_time (const char    *time_str,
                                     int           *hour_out,
                                     int           *minute_out,
                                     int           *second_out)
{
  if (time_str[0] == '\0' || time_str[1] == '\0'
   || time_str[2] != ':'
   || time_str[3] == '\0' || time_str[4] == '\0'
   || time_str[5] != ':'
   || time_str[6] == '\0' || time_str[7] == '\0')
    {
      DEBUG_DATE (("invalid format for military time: expected HH:MM:SS"));
      return FALSE;
    }

  *hour_out = strtol (time_str + 0, NULL, 10);
  *minute_out = strtol (time_str + 3, NULL, 10);
  *second_out = strtol (time_str + 6, NULL, 10);
  return TRUE;
}

static int parse_timezone (const char *timezone_str)
{
  int i;
  while (*timezone_str != '\0' && isspace (*timezone_str))
    timezone_str++;
  
  if (*timezone_str == '-' || *timezone_str == '+' || isdigit (*timezone_str))
    {
      /* parse -0600 style timezone */
      gboolean positive = TRUE;
      char hour[3], min[3];
      int rv;
      if (*timezone_str == '-')
        {
	  positive = FALSE;
	  timezone_str++;
	}
      else if (*timezone_str == '+')
        {
	  positive = TRUE;
	  timezone_str++;
	}
      if (timezone_str[0] == 0 || timezone_str[1] == 0
       || timezone_str[2] == 0 || timezone_str[3] == 0)
	{
	  /* XXX: error handling */
	  return 0;
	}
      hour[0] = timezone_str[0];
      hour[1] = timezone_str[1];
      hour[2] = '\0';
      min[0] = timezone_str[2];
      min[1] = timezone_str[3];
      min[2] = '\0';
      rv = (int) (strtol (hour, NULL, 10) * 60 + strtol (min, NULL, 10));
      if (!positive)
        rv = (-rv);
      return rv;
    }

  if (timezone_str[0] == 0 || timezone_str[1] == 0 || timezone_str[2] == 0)
    {
      /* XXX: error handling */
      return 0;
    }
  i = (guint8) (tolower (timezone_str[0]));
  i |= (int) ((guint8) (tolower (timezone_str[1]))) << 8;
  i |= (int) ((guint8) (tolower (timezone_str[2]))) << 16;
  return GPOINTER_TO_INT (g_hash_table_lookup (time_offset_from_name,
                                               GINT_TO_POINTER (i)));
}
  

/* rfc 822, obsoleted by rfc 1123:
 *     Sun, 06 Nov 1994 08:49:37 GMT
 */
static gboolean parse_1123 (const char *date,
                            struct tm  *out,
                            int        *tzoffset_out)
{
  int day_of_week;
  int day_of_month;
  int month;
  int year;
  int hour, minute, second;
  int index = 0;
  char *endp;

  day_of_week = get_day_of_week (date);
  if (day_of_week == 0)
    {
      DEBUG_DATE (("parse_1123: couldn't get day-of-week"));
      return FALSE;
    }
  if (date[3] != ',')
    {
      DEBUG_DATE (("parse_1123: missing ',' after day-of-week"));
      return FALSE;
    }
  index = 4;
  while (date[index] != '\0' && isspace (date[index]))
    index++;
  day_of_month = strtol (date + index, &endp, 10);
  if (endp == date + index)
    {
      DEBUG_DATE (("parse_1123: day-of-month number invalid"));
      return FALSE;
    }
  index = endp - date;
  while (date[index] != '\0' && isspace (date[index]))
    index++;
  month = get_month (date + index);
  index += 4;
  while (date[index] != '\0' && isspace (date[index]))
    index++;
  year = strtol (date + index, &endp, 10);
  if (endp == date + index)
    {
      DEBUG_DATE (("parse_1123: year number invalid"));
      return FALSE;
    }
  index = endp - date;
  while (date[index] != '\0' && isspace (date[index]))
    index++;
  if (!parse_military_time (date + index, &hour, &minute, &second))
    {
      DEBUG_DATE (("parse_1123: parse military time failed"));
      return FALSE;
    }
  index += 8;
  while (date[index] != '\0' && isspace (date[index]))
    index++;
  *tzoffset_out = parse_timezone (date + index);

  /* hm, correct for likely y2k annoyances */
  if (year < 1900)
    year += 1900;

  out->tm_sec = second;
  out->tm_min = minute;
  out->tm_hour = hour;
  out->tm_mday = day_of_month;
  out->tm_mon = month - 1;
  out->tm_year = year - 1900;
  out->tm_wday = day_of_week - 1;		/* ignored */
  out->tm_isdst = 0;
  return TRUE;
}

/* rfc 850, obsoleted by rfc 1036:
 *     Sunday, 06-Nov-94 08:49:37 GMT
 */
static gboolean parse_1036 (const char *date,
                            struct tm  *out,
                            int        *tzoffset_out)
{
  int day_of_week;
  int day_of_month;
  int month;
  int year;
  int hour, minute, second;
  int index = 0;

  day_of_week = get_day_of_week (date);
  if (day_of_week == 0)
    {
      DEBUG_DATE (("parse_1036: couldn't get day-of-week"));
      return FALSE;
    }
  while (date[index] != '\0' && isalpha (date[index]))
    index++;
  if (date[index] != ',')
    {
      DEBUG_DATE (("parse_1036: missing ',' after day-of-week"));
      return FALSE;
    }
  index++;
  while (date[index] != '\0' && isspace (date[index]))
    index++;
  day_of_month = strtol (date + index, NULL, 10);
  if (date[index + 2] != '-' || date[index + 6] != '-')
    {
      DEBUG_DATE (("parse_1036: missing '-' after day-of-month or month"));
      return FALSE;
    }
  month = get_month (date + index + 3);
  if (month == 0)
    return FALSE;
  year = strtol (date + index + 7, NULL, 10);
  if (year < 1900)
    year += 1900;
  index += 9;
  while (date[index] != '\0' && isdigit (date[index]))
    index++;
  while (date[index] != '\0' && isspace (date[index]))
    index++;
  if (!parse_military_time (date + index, &hour, &minute, &second))
    {
      DEBUG_DATE (("parse_1123: parse military time failed"));
      return FALSE;
    }
  index += 8;
  while (date[index] != '\0' && isspace (date[index]))
    index++;
  *tzoffset_out = parse_timezone (date + index);

  out->tm_sec = second;
  out->tm_min = minute;
  out->tm_hour = hour;
  out->tm_mday = day_of_month;
  out->tm_mon = month - 1;
  out->tm_year = year - 1900;
  out->tm_wday = day_of_week - 1;		/* ignored */
  out->tm_isdst = 0;
  return TRUE;
}

/* ansi c's asctime () format:
 *     Sun Nov  6 08:49:37 1994
 */
static gboolean parse_ansi_c (const char *date,
                              struct tm  *out,
			      int        *tzoffset_out)
{
  int day_of_week;
  int day_of_month;
  int month;
  int year;
  int hour, minute, second;
  int index = 0;
  char *endp;

  /* XXX: i guess assuming the local timezone would be best ? */
  *tzoffset_out = 0;

  day_of_week = get_day_of_week (date);
  if (day_of_week == 0)
    {
      DEBUG_DATE (("parse_ansi_c: couldn't get day-of-week"));
      return FALSE;
    }
  index = 3;
  while (date[index] != '\0' && isspace (date[index]))
    index++;
  month = get_month (date + index);
  if (month == 0)
    {
      DEBUG_DATE (("parse_ansi_c: couldn't get month"));
      return FALSE;
    }
  index += 3;
  while (date[index] != '\0' && isspace (date[index]))
    index++;
  day_of_month = strtol (date + index, &endp, 10);
  if (date + index == endp)
    {
      DEBUG_DATE (("parse_ansi_c: couldn't get day-of-month"));
      return FALSE;
    }
  index = endp - date;
  while (date[index] != '\0' && isspace (date[index]))
    index++;
  
  if (!parse_military_time (date + index, &hour, &minute, &second))
    {
      DEBUG_DATE (("parse_ansi_c: parse military time failed"));
      return FALSE;
    }

  index += 8;
  while (date[index] != '\0' && isspace (date[index]))
    index++;
  year = strtol (date + index, NULL, 10);

  if (year == 0)
    {
      DEBUG_DATE (("parse_ansi_c: year number invalid"));
      return FALSE;
    }

  out->tm_sec = second;
  out->tm_min = minute;
  out->tm_hour = hour;
  out->tm_mday = day_of_month;
  out->tm_mon = month - 1;
  out->tm_year = year - 1900;
  out->tm_wday = day_of_week - 1;		/* ignored */
  out->tm_isdst = 0;
  return TRUE;
}

static guint
parse_nums (const char *at,
	    guint      *n_chars_used,
	    char        sep,
	    guint      *nums_out,
	    guint      *digits_per_num_out,
	    guint       max_nums)
{
  guint num = 0;
  guint num_digits = 0;
  guint n_nums = 0;
  guint n_chars = 0;
  gboolean last_was_digit = FALSE;
  while (at[n_chars]
      && at[n_chars] != ' '
      && n_nums < 4)
    {
      if (isdigit (at[n_chars]))
	{
	  if (last_was_digit)
	    num *= 10;
	  else
	    last_was_digit = TRUE;
	  num += at[n_chars] - '0';
	  num_digits++;
	}
      else if (last_was_digit)
	{
	  nums_out[n_nums] = num;
	  digits_per_num_out[n_nums] = num_digits;
	  n_nums++;
	  num = 0;
	  num_digits = 0;
	  last_was_digit = FALSE;
	}

      if (!last_was_digit && at[n_chars] != sep)
	{
	  break;
	}
      n_chars++;
    }
  if (last_was_digit)
    {
      nums_out[n_nums] = num;
      digits_per_num_out[n_nums] = num_digits;
      n_nums++;
    }
  *n_chars_used = n_chars;
  return n_nums;
}

/* See http://www.cl.cam.ac.uk/%7Emgk25/iso-time.html */
static gboolean
parse_iso8601 (const char *date,
	       struct tm  *out,
	       int        *tzoffset_out)
{
  guint n_date_chars = 0;
  guint nums[5];
  guint digits_per_num[5];
  guint year;
  guint month = 1;
  guint day = 1;
  gint day_of_year = -1;
  guint hours = 0;
  guint minutes = 0;
  guint seconds = 0;
  guint timezone = 0;
  guint n_nums = parse_nums (date, &n_date_chars, '-', nums, digits_per_num, 5);
  guint n_digits = 0;
  guint i;
  for (i = 0; i < n_nums; i++)
    n_digits += digits_per_num[i];
  if (n_digits == 2)
    {
      /* YY */
      if (n_nums == 1)
	year = 1900 + nums[0];
      else
	{
	  DEBUG_DATE(("got 2-char date spec with wrong number of numbers"));
	  return FALSE;
	}
    }
  else if (n_digits == 4)
    {
      /* YYYY or YY:MM */
      if (n_nums == 1)
	year = nums[0];
      else if (n_nums == 2)
	{
	  if (digits_per_num[0] != 2)
            {
	      DEBUG_DATE(("got bad 4 digit date spec"));
	      return FALSE;
	    }
	  year = 1900 + nums[0];
	  month = nums[1];
	}
      else
	{
	  DEBUG_DATE(("got 4 char date, with non-2 numbers"));
	  return FALSE;
	}
    }
  else if (n_digits == 6)
    {
      /* YYYY:MM or YY:MM:DD */
      if (n_nums == 2)
	{
	  if (digits_per_num[0] != 4)
	    {
	      DEBUG_DATE(("6 digits, 2 parts, expected first to be year"));
	      return FALSE;
	    }
	  year = nums[0];
	  month = nums[1];
	}
      else if (n_nums == 3)
	{
	  year = 1900 + nums[0];
	  month = nums[1];
	  day = nums[2];
	}
      else
	return FALSE;
    }
  else if (n_date_chars == 7)
    {
      /* YYYY:DOY or YYYYDOY */
      if (n_nums == 1)
	{
	  year = nums[0] / 1000;
	  day_of_year = nums[0] % 1000;
	}
      else if (n_nums == 2)
	{
	  if (digits_per_num[0] != 4)
	    {
	      DEBUG_DATE(("7 digit date must be YEAR:DAT-OF-YEAR"));
	      return FALSE;
	    }
	  year = nums[0];
	  day_of_year = nums[1];
	}
      else
	{
	  DEBUG_DATE(("got 7 digit, %u number date spec", n_nums));
	  return FALSE;
	}
    }
  else if (n_digits == 8)
    {
      /* YYYYMMDD or YYYY:MM:DD */
      if (n_nums == 1)
	{
	  year = nums[0] / 10000;
	  month = nums[0] / 100 % 100;
	  day = nums[0] % 100;
	}
      else if (n_nums == 3)
	{
	  if (digits_per_num[0] != 4 || digits_per_num[1] != 2)
	    {
	      DEBUG_DATE(("got 8 digit date, with 3 number lengths %u,%u,%u; not allowed",
			  digits_per_num[0], digits_per_num[1], digits_per_num[2]));
	      return FALSE;
	    }
	  year = nums[0];
	  month = nums[1];
	  day = nums[2];
	}
      else
	{
	  DEBUG_DATE(("got 8 digit date, with %u parts", n_nums));
	  return FALSE;
	}
    }
  else
    return FALSE;

  if (date[n_date_chars] == 'T')
    n_date_chars++;
  else if (date[n_date_chars] == ' ')
    {
      /* skip whitespace */
      while (date[n_date_chars] && isspace (date[n_date_chars]))
	n_date_chars++;
      if (date[n_date_chars] != 0 && !isdigit (date[n_date_chars]))
	{
	  DEBUG_DATE (("parse_iso8601: bad character after day portion and whitespace"));
	  return FALSE;
	}
    }
  else
    {
      DEBUG_DATE (("parse_iso8601: bad character after day portion"));
      return FALSE;
    }

  if (date[n_date_chars] != '\0')
    {
      /* Parse time portion */
      const char *time = date + n_date_chars;
      guint n_time_chars;
      n_nums = parse_nums (time, &n_time_chars, ':', nums, digits_per_num, 5);
      if (n_nums == 3)
	{
	  hours = nums[0];
	  minutes = nums[1];
	  seconds = nums[2];
	}
      else if (n_nums == 2)
	{
	  hours = nums[0];
	  minutes = nums[1];
	}
      else if (n_nums == 1)
	{
	  if (digits_per_num[0] == 2)
	    hours = nums[0];
	  else if (digits_per_num[0] == 4)
	    {
	      hours = nums[0] / 100;
	      minutes = nums[0] % 100;
	    }
	  else if (digits_per_num[0] == 6)
	    {
	      hours = nums[0] / 100 / 100;
	      minutes = nums[0] / 100 % 100;
	      seconds = nums[0] % 100;
	    }
	  else
	    {
	      DEBUG_DATE(("time spec had 1 number with %u digits", digits_per_num[0]));
	      return FALSE;
	    }
	}
      else
	{
	  DEBUG_DATE(("time spec had %u numbers", n_nums));
	  return FALSE;
	}

      if (time[n_time_chars] == '.')
	{
	  /* fraction of seconds... skip */
	  n_time_chars++;
	  while (time[n_time_chars] && isdigit (time[n_time_chars]))
	    n_time_chars++;
	}

      if (time[n_time_chars] == 'Z')
	{
	  /* fine, UTC which is default */
	}
      else if (time[n_time_chars] == '-'
            || time[n_time_chars] == '+')
	{
	  /* timezone.  either +HH:MM or +HH or +HHMM or '-' variants */
	  int pm = time[n_time_chars] == '+' ? 1 : -1;
	  int hoffset = 0;
	  int moffset = 0;
	  n_time_chars++;
	  if (!isdigit (time[n_time_chars])
	   || !isdigit (time[n_time_chars+1]))
	   return FALSE;
	  hoffset = (time[n_time_chars] - '0') * 10 + (time[n_time_chars+1] - '0');
	  n_time_chars += 2;
	  if (time[n_time_chars] == ':')
	    n_time_chars++;
	  if (time[n_time_chars]
           && isdigit (time[n_time_chars])
	   && isdigit (time[n_time_chars+1]))
	    moffset = (time[n_time_chars] - '0') * 10 + (time[n_time_chars+1] - '0');
	  timezone = pm * (hoffset * 60 + moffset);
	}
      else if (time[n_time_chars] != 0 && !isspace (time[n_time_chars]))
	{
	  DEBUG_DATE (("parse_iso8601: bad character after time portion"));
	  return FALSE;
	}
    }

  /* fill out out and tzoffset_out */
  *tzoffset_out = timezone;

  out->tm_year = year - 1900;
  out->tm_mon = month - 1;
  out->tm_mday = day;
  if (day_of_year >= 0)
    {
      out->tm_yday = day_of_year;

      /* how to compute tm_mday, tm_mon??? */
      out->tm_mday = 0;
      g_warning ("need day-of-year to day-of-month and month routine");
    }

  /* TODO: compute wday... */

  out->tm_min = minutes;
  out->tm_hour = hours;
  out->tm_sec = seconds;

  return TRUE;
}


/**
 * gsk_date_parse:
 * @date_str: the string containing a date.
 * @tm_out: location to store the time, as a struct tm.
 * (That is, all the fields are broken out).
 * @tzoffset_out: location to store the timezone offset.
 * (offset stored in minutes)
 * @formats_allowed: bitwise-OR of all the allowed date formats.
 * The parser will try to find a date in any of the allowed formats.
 *
 * Parse a date to a struct tm.
 *
 * returns: whether the date was successfully parsed.
 */
gboolean gsk_date_parse            (const char        *date_str,
                                    struct tm         *tm_out,
				    int               *tzoffset_out,
				    GskDateFormatMask  allowed)
{
  init_tables ();
  if (date_str[0] == '\0'
   || date_str[1] == '\0'
   || date_str[2] == '\0'
   || date_str[3] == '\0')
    return FALSE;
  if (isalpha (date_str[0]) && isupper (date_str[0])
   && isalpha (date_str[1]) && islower (date_str[1])
   && isalpha (date_str[2]) && islower (date_str[2]))
    {
      if (isspace (date_str[3])
       && (allowed & GSK_DATE_FORMAT_ANSI_C) == GSK_DATE_FORMAT_ANSI_C)
        return parse_ansi_c (date_str, tm_out, tzoffset_out);
      if (date_str[3] == ','
       && (allowed & GSK_DATE_FORMAT_1123) == GSK_DATE_FORMAT_1123)
        return parse_1123 (date_str, tm_out, tzoffset_out);
      if (isalpha (date_str[3]) && islower (date_str [3])
       && (allowed & GSK_DATE_FORMAT_1036) == GSK_DATE_FORMAT_1036)
        return parse_1036 (date_str, tm_out, tzoffset_out);
    }
  if (isdigit (date_str[0])
   && isdigit (date_str[1])
   && (allowed & GSK_DATE_FORMAT_ISO8601) == GSK_DATE_FORMAT_ISO8601)
    {
      return parse_iso8601 (date_str, tm_out, tzoffset_out);
    }
  return FALSE;
}


/**
 * gsk_date_parse_timet:
 * @date_str: the string containing a date.
 * @out: location to store the time, as a unix time.
 * That is, the time since the start of 1970 GMT.
 * @formats_allowed: bitwise-OR of all the allowed date formats.
 * The parser will try to find a date in any of the allowed formats.
 *
 * Parse a date to a unix time.
 *
 * returns: whether the date was successfully parsed.
 */
gboolean gsk_date_parse_timet      (const char        *date_str,
                                    time_t            *out,
				    GskDateFormatMask  formats_allowed)
{
  struct tm tm_out;
  int tz_offset;
#if USE_LOCALTIME_HACK
  struct tm new_tm;
#endif
  if (!gsk_date_parse (date_str, &tm_out, &tz_offset, formats_allowed))
    return FALSE;
  
#if USE_LOCALTIME_HACK
  *out = mktime (&tm_out);
  localtime_r (out, &new_tm);
  if (new_tm.tm_isdst)
    *out += 3600;
  *out -= timezone;
#else
  *out = gsk_timegm (&tm_out);
  if (*out == ((time_t)-1))
    return FALSE;
#endif

  *out -= tz_offset * 60;
  return TRUE;
}

static const char *day_of_week_to_full_name[] =
{
  "Sunday",
  "Monday",
  "Tuesday",
  "Wednesday",
  "Thursday",
  "Friday",
  "Saturday"
};

static const char *day_of_week_to_three_letter_stud_capped[] =
{
  "Sun",
  "Mon",
  "Tue",
  "Wed",
  "Thu",
  "Fri",
  "Sat"
};

static const char *month_to_three_letter_stud_capped[] =
{
  "Jan",
  "Feb",
  "Mar",
  "Apr",
  "May",
  "Jun",
  "Jul",
  "Aug",
  "Sep",
  "Oct",
  "Nov",
  "Dec"
};

/**
 * gsk_date_print:
 * @tm: the time, separated into pieces.
 * (All fields, even derived fields like tm_wday, must be set.)
 * @date_str_out: buffer to fill with the date as a string.
 * @date_str_max_len: the length of @date_str_out.  This should be 80 or greater
 * to prevent clipping.
 * @format: which presentation of the date to use.
 *
 * Print the date to a buffer, in the format requested.
 */
void     gsk_date_print            (const struct tm   *tm,
                                    char              *date_str_out,
				    int                date_str_max_len,
				    GskDateFormatMask  format)
{
  if ((format & GSK_DATE_FORMAT_1036) == GSK_DATE_FORMAT_1036)
    {
      g_snprintf (date_str_out, date_str_max_len,
                  "%s, %02d-%s-%d %02d:%02d:%02d GMT",
		  day_of_week_to_full_name[tm->tm_wday],
		  tm->tm_mday,
		  month_to_three_letter_stud_capped[tm->tm_mon],
		  tm->tm_year,
		  tm->tm_hour,
		  tm->tm_min,
		  tm->tm_sec);
      return;
    }
  if ((format & GSK_DATE_FORMAT_1123) == GSK_DATE_FORMAT_1123)
    {
      g_snprintf (date_str_out, date_str_max_len,
                  "%s, %02d %s %d %02d:%02d:%02d GMT",
		  day_of_week_to_three_letter_stud_capped[tm->tm_wday],
		  tm->tm_mday,
		  month_to_three_letter_stud_capped[tm->tm_mon],
		  tm->tm_year + 1900,
		  tm->tm_hour,
		  tm->tm_min,
		  tm->tm_sec);
      return;
    }
  if ((format & GSK_DATE_FORMAT_ANSI_C) == GSK_DATE_FORMAT_ANSI_C)
    {
      g_snprintf (date_str_out, date_str_max_len,
                  "%s %s %2d %02d:%02d:%02d %d GMT",
		  day_of_week_to_three_letter_stud_capped[tm->tm_wday],
		  month_to_three_letter_stud_capped[tm->tm_mon],
		  tm->tm_mday,
		  tm->tm_hour,
		  tm->tm_min,
		  tm->tm_sec,
		  tm->tm_year + 1900);
      return;
    }
  if ((format & GSK_DATE_FORMAT_ISO8601) == GSK_DATE_FORMAT_ISO8601)
    {
      /* Use the compact 8601 representation.
         See http://www.cl.cam.ac.uk/%7Emgk25/iso-time.html */
      g_snprintf (date_str_out, date_str_max_len,
		  "%04u%02u%02uT%02u%02u%02uZ",
		  tm->tm_year + 1900,
		  tm->tm_mon + 1,
		  tm->tm_mday,
		  tm->tm_hour,
		  tm->tm_min,
		  tm->tm_sec);
      return;
    }

  g_warning ("gsk_date_print: GSK_DATE_FORMAT_* expected");
  g_snprintf (date_str_out, date_str_max_len, "error");
}

/**
 * gsk_date_print_timet:
 * @t: the time, as per unix tradition.  That is, this is the
 * time since the beginning of 1970 GMT.
 * @date_str_out: buffer to fill with the date as a string.
 * @date_str_max_len: the length of @date_str_out.  This should be 80 or greater
 * to prevent clipping.
 * @format: which presentation of the date to use.
 *
 * Print the date to a buffer, in the format requested.
 */
void     gsk_date_print_timet      (time_t             t,
                                    char              *date_str_out,
				    int                date_str_max_len,
				    GskDateFormatMask  format)
{
  /* XXX: there is a gmtime_r function we should use on some systems. */
  struct tm *tm_out;
  tm_out = gmtime (&t);
  g_return_if_fail (tm_out != NULL);
  gsk_date_print (tm_out, date_str_out, date_str_max_len, format);
}


syntax highlighted by Code2HTML, v. 0.9.1