pennmush/game/data/
pennmush/game/log/
pennmush/game/save/
pennmush/game/txt/evt/
pennmush/game/txt/nws/
pennmush/os2/
pennmush/po/
pennmush/win32/msvc.net/
pennmush/win32/msvc6/
/**
 * \file funtime.c
 *
 * \brief Time functions for mushcode.
 *
 *
 */
#include "copyrite.h"

#include "config.h"
#include <string.h>
#include <ctype.h>
#if  (defined(__GNUC__) || defined(__LCC__)) && !defined(__USE_XOPEN_EXTENDED)
/* Required to get the getdate() prototype on glibc. */
#define __USE_XOPEN_EXTENDED
#endif
#include <time.h>
#include <errno.h>
#include "conf.h"
#include "externs.h"
#include "parse.h"
#include "dbdefs.h"
#include "log.h"
#include "match.h"
#include "confmagic.h"

int do_convtime(const char *str, struct tm *ttm);
void do_timestring(char *buff, char **bp, const char *format,
		   unsigned long secs);

extern char valid_timefmt_codes[256];

FUNCTION(fun_timefmt)
{
  char s[BUFFER_LEN];
  struct tm *ttm;
  time_t tt;
  int len, n;

  if (!args[0] || !*args[0])
    return;			/* No field? Bad user. */

  if (nargs == 2) {
    /* This is silly, but time_t is signed on several platforms,
     * so we can't assign an unsigned int to it safely
     */
    if (!is_integer(args[1])) {
      safe_str(T(e_uint), buff, bp);
      return;
    }
    tt = parse_integer(args[1]);
    if (errno == ERANGE) {
      safe_str(T(e_range), buff, bp);
      return;
    }
    if (tt < 0) {
      safe_str(T(e_uint), buff, bp);
      return;
    }
  } else
    tt = mudtime;

  ttm = localtime(&tt);
  len = arglens[0];
  for (n = 0; n < len; n++) {
    if (args[0][n] == '%')
      args[0][n] = 0x5;
    else if (args[0][n] == '$') {
      args[0][n] = '%';
      n++;
      if (args[0][n] == '$')
	args[0][n] = '%';
      else if (!valid_timefmt_codes[(unsigned char) args[0][n]]) {
	safe_format(buff, bp, T("#-1 INVALID ESCAPE CODE '$%c'"),
		    args[0][n] ? args[0][n] : ' ');
	return;
      }
    }
  }
  len = strftime(s, BUFFER_LEN, args[0], ttm);
  if (len == 0) {
    /* Problem. Either the output from strftime would be over
     * BUFFER_LEN characters, or there wasn't any output at all.
     * In the former case, what's in s is indeterminate. Instead of
     * trying to figure out which of the two cases happened, just
     * return an empty string.
     */
    return;
  }
  for (n = 0; n < len; n++)
    if (s[n] == '%')
      s[n] = '$';
    else if (s[n] == 0x5)
      s[n] = '%';
  safe_str(s, buff, bp);
}

/* ARGSUSED */
FUNCTION(fun_time)
{
  int utc = 0;
  int mytime;
  double tz = 0;

  mytime = mudtime;

  if (nargs == 1) {
    if (!strcasecmp("UTC", args[0])) {
      utc = 1;
    } else if (args[0] && *args[0] && is_strict_number(args[0])) {
      utc = 1;
      tz = strtod(args[0], NULL);
      if (tz < -24.0 || tz > 24.0) {
	safe_str(T("#-1 INVALID TIME ZONE"), buff, bp);
	return;
      }
      mytime += (int) (tz * 3600);
    } else if (args[0] && *args[0]) {
      dbref thing;
      ATTR *a;
      char *ptr;
      utc = 1;
      thing = match_thing(executor, args[0]);
      if (!GoodObject(thing)) {
	safe_str(T(e_notvis), buff, bp);
	return;
      }
      /* Always make time(player) return a time,
       * even if player's TZ is unset or wonky */
      a = atr_get(thing, "TZ");
      if (a && is_strict_number(ptr = atr_value(a))) {
	tz = strtod(ptr, NULL);
	if (tz >= -24.0 || tz <= 24.0) {
	  mytime += (int) (tz * 3600);
	}
      }
    }
  } else if (!strcmp("UTCTIME", called_as)) {
    utc = 1;
  }

  safe_str(show_time(mytime, utc), buff, bp);
}

/* ARGSUSED */
FUNCTION(fun_secs)
{
  safe_integer(mudtime, buff, bp);
}

/* ARGSUSED */
FUNCTION(fun_convsecs)
{
  /* converts seconds to a time string */

  time_t tt;
  struct tm *ttm;
  int utc = 0;

  if (strcmp(called_as, "CONVUTCSECS") == 0 ||
      (nargs == 2 && strcasecmp("UTC", args[1]) == 0))
    utc = 1;

  if (!is_integer(args[0])) {
    safe_str(T(e_int), buff, bp);
    return;
  }
  tt = parse_integer(args[0]);
  if (errno == ERANGE) {
    safe_str(T(e_range), buff, bp);
    return;
  }
  if (tt < 0) {
    safe_str(T(e_uint), buff, bp);
    return;
  }

  if (utc)
    ttm = gmtime(&tt);
  else
    ttm = localtime(&tt);

  safe_str(show_tm(ttm), buff, bp);
}

/* ARGSUSED */
FUNCTION(fun_etimefmt)
{
  unsigned long secs;

  if (!is_uinteger(args[1])) {
    safe_str(e_uint, buff, bp);
    return;
  }

  secs = parse_uinteger(args[1]);

  do_timestring(buff, bp, args[0], secs);

}

/* ARGSUSED */
FUNCTION(fun_timestring)
{
  /* Convert seconds to #d #h #m #s
   * If pad > 0, pad with 0's (i.e. 0d 0h 5m 1s)
   * If pad > 1, force all #'s to be 2 digits
   */
  unsigned int secs, pad;
  unsigned int days, hours, mins;

  if (!is_uinteger(args[0])) {
    safe_str(T(e_uints), buff, bp);
    return;
  }
  if (nargs == 1)
    pad = 0;
  else {
    if (!is_uinteger(args[1])) {
      safe_str(T(e_uints), buff, bp);
      return;
    }
    pad = parse_uinteger(args[1]);
  }

  secs = parse_uinteger(args[0]);

  days = secs / 86400;
  secs %= 86400;
  hours = secs / 3600;
  secs %= 3600;
  mins = secs / 60;
  secs %= 60;
  if (pad || (days > 0)) {
    if (pad == 2)
      safe_format(buff, bp, "%02ud %02uh %02um %02us", days, hours, mins, secs);
    else
      safe_format(buff, bp, "%ud %2uh %2um %2us", days, hours, mins, secs);
  } else if (hours > 0)
    safe_format(buff, bp, "%2uh %2um %2us", hours, mins, secs);
  else if (mins > 0)
    safe_format(buff, bp, "%2um %2us", mins, secs);
  else
    safe_format(buff, bp, "%2us", secs);
}

#ifdef HAS_GETDATE
int do_convtime_gd(const char *str, struct tm *ttm);
/** Convert a time string to a struct tm using getdate().
 * Formats for the time string are taken from the file referenced in
 * the DATEMSK environment variable.
 * \param str a time string.
 * \param ttm pointer to a struct tm to fill.
 * \retval 1 success.
 * \retval 0 failure.
 */
int
do_convtime_gd(const char *str, struct tm *ttm)
{
  /* converts time string to a struct tm. Returns 1 on success, 0 on fail.
   * Formats of the time string are taken from the file listed in the
   * DATEMSK env variable 
   */
  struct tm *tc;

  tc = getdate(str);

  if (tc == NULL) {
#ifdef NEVER
    if (getdate_err <= 7)
      do_rawlog(LT_ERR, "getdate returned error code %d for %s", getdate_err,
		str);
#endif
    return 0;
  }

  memcpy(ttm, tc, sizeof(struct tm));
  ttm->tm_isdst = -1;

  return 1;
}

#endif
/* do_convtime for systems without getdate(). Will probably break if in
         a non en_US locale */
static const char *month_table[] = {
  "Jan",
  "Feb",
  "Mar",
  "Apr",
  "May",
  "Jun",
  "Jul",
  "Aug",
  "Sep",
  "Oct",
  "Nov",
  "Dec",
};

/** Convert a time string to a struct tm, without getdate().
 * \param mystr a time string.
 * \param ttm pointer to a struct tm to fill.
 * \retval 1 success.
 * \retval 0 failure.
 */
int
do_convtime(const char *mystr, struct tm *ttm)
{
  /* converts time string to a struct tm. Returns 1 on success, 0 on fail.
   * Time string format is always 24 characters long, in format
   * Ddd Mmm DD HH:MM:SS YYYY
   */

  char *p, *q;
  char str[25];
  int i;

  if (strlen(mystr) != 24)
    return 0;
  strcpy(str, mystr);

  /* move over the day of week and truncate. Will always be 3 chars.
   * we don't need this, so we can ignore it.
   */
  if (!(p = strchr(str, ' ')))
    return 0;
  *p++ = '\0';
  if (strlen(str) != 3)
    return 0;

  /* get the month (3 chars), and convert it to a number */
  if (!(q = strchr(p, ' ')))
    return 0;
  *q++ = '\0';
  if (strlen(p) != 3)
    return 0;
  for (i = 0; (i < 12) && strcmp(month_table[i], p); i++) ;
  if (i == 12)			/* not found */
    return 0;
  else
    ttm->tm_mon = i;

  /* get the day of month */
  p = q;
  while (isspace((unsigned char) *p))	/* skip leading space */
    p++;
  if (!(q = strchr(p, ' ')))
    return 0;
  *q++ = '\0';
  ttm->tm_mday = atoi(p);

  /* get hours */
  if (!(p = strchr(q, ':')))
    return 0;
  *p++ = '\0';
  ttm->tm_hour = atoi(q);

  /* get minutes */
  if (!(q = strchr(p, ':')))
    return 0;
  *q++ = '\0';
  ttm->tm_min = atoi(p);

  /* get seconds */
  if (!(p = strchr(q, ' ')))
    return 0;
  *p++ = '\0';
  ttm->tm_sec = atoi(q);

  /* get year */
  ttm->tm_year = atoi(p) - 1900;

  ttm->tm_isdst = -1;

  return 1;
}

/* ARGSUSED */
FUNCTION(fun_convtime)
{
  /* converts time string to seconds */

  struct tm ttm;

  if (do_convtime(args[0], &ttm)
#ifdef HAS_GETDATE
      || do_convtime_gd(args[0], &ttm)
#endif
    ) {
#ifdef SUN_OS
    safe_integer(timelocal(&ttm), buff, bp);
#else
    safe_integer(mktime(&ttm), buff, bp);
#endif				/* SUN_OS */
  } else {
    safe_str("-1", buff, bp);
  }
}

#ifdef WIN32
#pragma warning( disable : 4761)	/* NJG: disable warning re conversion */
#endif
/* ARGSUSED */
FUNCTION(fun_isdaylight)
{
  struct tm *ltime;

  ltime = localtime(&mudtime);

  safe_boolean(ltime->tm_isdst > 0, buff, bp);
}

/** Convert seconds to a formatted time string.
 * \verbatim
 * Format codes:
 *       $s - Seconds. $S - Seconds, force 2 digits.
 *       $m - Minutes. $M - Minutes, force 2 digits.
 *       $h - Hours.   $H - Hours, force 2 digits.
 *       $d - Days.    $D - Days, force 2 digits.
 * $$ - Literal $.
 * \endverbatim
 * \param buff string to store the result in.
 * \param bp pointer into end of buff.
 * \param format format code string.
 * \param secs seconds to convert.
 */
void
do_timestring(char *buff, char **bp, const char *format, unsigned long secs)
{
  int days, hours, mins;
  int pad = 0;
  int width;
  const char *c;
  char *w;
  int include_suffix, even_if_0, in_format_flags;

  days = secs / 86400;
  secs %= 86400;
  hours = secs / 3600;
  secs %= 3600;
  mins = secs / 60;
  secs %= 60;

  for (c = format; c && *c; c++) {
    if (*c == '$') {
      c++;
      width = strtol(c, &w, 10);
      if (c == w)
	pad = 0;
      else
	pad = 1;
      if (width < 0)
	width = 0;
      else if (width >= BUFFER_LEN)
	width = BUFFER_LEN - 1;
      even_if_0 = in_format_flags = 1;
      include_suffix = 0;
      while (in_format_flags) {
	switch (*w) {
	case 'x':
	case 'X':
	  include_suffix = 1;
	  w++;
	  break;
	case 'z':
	case 'Z':
	  even_if_0 = 0;
	  w++;
	  break;
	case '$':
	  in_format_flags = 0;
	  if (pad)
	    safe_format(buff, bp, "%*c", width, '$');
	  else
	    safe_chr('$', buff, bp);
	  break;
	case 's':
	  in_format_flags = 0;
	  if (secs || even_if_0) {
	    if (pad)
	      safe_format(buff, bp, "%*lu", width, secs);
	    else
	      safe_uinteger(secs, buff, bp);
	    if (include_suffix)
	      safe_chr('s', buff, bp);
	  } else if (pad)
	    safe_fill(' ', width + (include_suffix ? 1 : 0), buff, bp);
	  break;
	case 'S':
	  in_format_flags = 0;
	  if (secs || even_if_0) {
	    if (pad)
	      safe_format(buff, bp, "%0*lu", width, secs);
	    else
	      safe_format(buff, bp, "%0lu", secs);
	    if (include_suffix)
	      safe_chr('s', buff, bp);
	  } else if (pad)
	    safe_fill(' ', width + (include_suffix ? 1 : 0), buff, bp);
	  break;
	case 'm':
	  in_format_flags = 0;
	  if (mins || even_if_0) {
	    if (pad)
	      safe_format(buff, bp, "%*d", width, mins);
	    else
	      safe_integer(mins, buff, bp);
	    if (include_suffix)
	      safe_chr('m', buff, bp);
	  } else if (pad)
	    safe_fill(' ', width + (include_suffix ? 1 : 0), buff, bp);
	  break;
	case 'M':
	  in_format_flags = 0;
	  if (mins || even_if_0) {
	    if (pad)
	      safe_format(buff, bp, "%0*d", width, mins);
	    else
	      safe_format(buff, bp, "%0d", mins);
	    if (include_suffix)
	      safe_chr('m', buff, bp);
	  } else if (pad)
	    safe_fill(' ', width + (include_suffix ? 1 : 0), buff, bp);
	  break;
	case 'h':
	  in_format_flags = 0;
	  if (hours || even_if_0) {
	    if (pad)
	      safe_format(buff, bp, "%*d", width, hours);
	    else
	      safe_integer(hours, buff, bp);
	    if (include_suffix)
	      safe_chr('h', buff, bp);
	  } else if (pad)
	    safe_fill(' ', width + (include_suffix ? 1 : 0), buff, bp);
	  break;
	case 'H':
	  in_format_flags = 0;
	  if (hours || even_if_0) {
	    if (pad)
	      safe_format(buff, bp, "%0*d", width, hours);
	    else
	      safe_format(buff, bp, "%0d", hours);
	    if (include_suffix)
	      safe_chr('h', buff, bp);
	  } else if (pad)
	    safe_fill(' ', width + (include_suffix ? 1 : 0), buff, bp);
	  break;
	case 'd':
	  in_format_flags = 0;
	  if (days || even_if_0) {
	    if (pad)
	      safe_format(buff, bp, "%*d", width, days);
	    else
	      safe_integer(days, buff, bp);
	    if (include_suffix)
	      safe_chr('d', buff, bp);
	  } else if (pad)
	    safe_fill(' ', width + (include_suffix ? 1 : 0), buff, bp);
	  break;
	case 'D':
	  in_format_flags = 0;
	  if (days || even_if_0) {
	    if (pad)
	      safe_format(buff, bp, "%0*d", width, days);
	    else
	      safe_format(buff, bp, "%0d", days);
	    if (include_suffix)
	      safe_chr('d', buff, bp);
	  } else if (pad)
	    safe_fill(' ', width + (include_suffix ? 1 : 0), buff, bp);
	  break;
	default:
	  in_format_flags = 0;
	  safe_chr('$', buff, bp);
	  for (; c != w; c++)
	    safe_chr(*c, buff, bp);
	  safe_chr(*c, buff, bp);
	}
      }
      c = w;
    } else
      safe_chr(*c, buff, bp);
  }
}

#ifdef WIN32
#pragma warning( default : 4761)	/* NJG: enable warning re conversion */
#endif