/*

  silctime.c

  Author: Pekka Riikonen <priikone@silcnet.org>

  Copyright (C) 2003 - 2007 Pekka Riikonen

  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; version 2 of the License.

  This program 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 General Public License for more details.

*/

#include "silc.h"

/* Fills the SilcTime structure with correct values */

static SilcBool silc_time_fill(SilcTime time,
			       unsigned int year,
			       unsigned int month,
			       unsigned int day,
			       unsigned int hour,
			       unsigned int minute,
			       unsigned int second,
			       unsigned int msec)
{
  if (year > (1 << 15))
    return FALSE;
  if (month < 1 || month > 12)
    return FALSE;
  if (day < 1 || day > 31)
    return FALSE;
  if (hour > 23)
    return FALSE;
  if (minute > 60)
    return FALSE;
  if (second > 61)
    return FALSE;
  if (msec > 1000)
    return FALSE;

  time->year = year;
  time->month = month;
  time->day = day;
  time->hour = hour;
  time->minute = minute;
  time->second = second;
  time->msecond = msec;

  return TRUE;
}

/* Return time since Epoch */

SilcInt64 silc_time(void)
{
  return (SilcInt64)time(NULL);
}

/* Return time since Epoch in milliseconds */

SilcInt64 silc_time_msec(void)
{
  struct timeval curtime;
  silc_gettimeofday(&curtime);
  return (curtime.tv_sec * (SilcUInt64)1000) +
    (curtime.tv_usec / (SilcUInt64)1000);
}

/* Return time since Epoch in microseconds */

SilcInt64 silc_time_usec(void)
{
  struct timeval curtime;
  silc_gettimeofday(&curtime);
  return (curtime.tv_sec * (SilcUInt64)1000000) + curtime.tv_usec;
}

/* Returns time as string */

const char *silc_time_string(SilcInt64 time_val)
{
  time_t curtime;
  char *return_time;

  if (!time_val)
    curtime = silc_time();
  else
    curtime = (time_t)time_val;
  return_time = ctime(&curtime);
  if (!return_time)
    return NULL;
  return_time[strlen(return_time) - 1] = '\0';

  return (const char *)return_time;
}

/* Returns time as SilcTime structure */

SilcBool silc_time_value(SilcInt64 time_val, SilcTime ret_time)
{
  struct tm *t;
  unsigned int msec = 0;
  time_t timeval;
  SilcInt32 ctz = 0;

  if (!ret_time)
    return TRUE;

  if (!time_val)
    time_val = silc_time_msec();

  msec = (SilcUInt64)time_val % (SilcUInt64)1000;
  timeval = (time_t)((SilcUInt64)time_val / (SilcUInt64)1000);

  t = localtime(&timeval);
  if (!t)
    return FALSE;

  memset(ret_time, 0, sizeof(*ret_time));
  if (!silc_time_fill(ret_time, t->tm_year + 1900, t->tm_mon + 1,
		      t->tm_mday, t->tm_hour, t->tm_min,
		      t->tm_sec, msec))
    return FALSE;

  ret_time->dst        = t->tm_isdst ? 1 : 0;

#ifdef SILC_WIN32
  ret_time->utc_east   = _timezone < 0 ? 1 : 0;
  ret_time->utc_hour   = (ret_time->utc_east ? (-(_timezone)) / 3600 :
			  _timezone / 3600);
  ret_time->utc_minute = (ret_time->utc_east ? (-(_timezone)) % 3600 :
			  _timezone % 3600);
#else
#if defined(HAVE_TIMEZONE)
  ret_time->utc_east   = timezone < 0 ? 1 : 0;
  ctz = timezone;
  if (ret_time->dst)
    ctz -= 3600;
#elif defined(HAVE_TM_GMTOFF)
  ret_time->utc_east   = t->tm_gmtoff > 0 ? 1 : 0;
  ctz = -t->tm_gmtoff;
#elif defined(HAVE___TM_GMTOFF)
  ret_time->utc_east   = t->__tm_gmtoff > 0 ? 1 : 0;
  ctz = -t->__tm_gmtoff;
#elif defined(HAVE___TM_GMTOFF__)
  ret_time->utc_east   = t->__tm_gmtoff__ > 0 ? 1 : 0;
  ctz = -t->__tm_gmtoff__;
#endif /* HAVE_TIMEZONE */

  ret_time->utc_hour   = (ret_time->utc_east ? (-(ctz)) / 3600 : ctz / 3600);
  ret_time->utc_minute = (ret_time->utc_east ? (-(ctz)) % 3600 : ctz % 3600);
#endif /* SILC_WIN32 */

  if (ret_time->utc_minute)
    ret_time->utc_minute /= 60;

  return TRUE;
}

/* Returns timezone */

SilcBool silc_timezone(char *timezone, SilcUInt32 timezone_size)
{
  SilcTimeStruct curtime;

  if (timezone_size < 6)
    return FALSE;

  if (!silc_time_value(0, &curtime))
    return FALSE;

  if (!curtime.utc_hour && curtime.utc_minute)
    silc_snprintf(timezone, timezone_size, "Z");
  else if (curtime.utc_minute)
    silc_snprintf(timezone, timezone_size, "%c%02d:%02d",
		  curtime.utc_east ? '+' : '-', curtime.utc_hour,
		  curtime.utc_minute);
  else
    silc_snprintf(timezone, timezone_size, "%c%02d",
		  curtime.utc_east ? '+' : '-', curtime.utc_hour);

  return TRUE;
}

/* Returns time from universal time string into SilcTime */

SilcBool silc_time_universal(const char *universal_time, SilcTime ret_time)
{
  int ret;
  unsigned int year, month, day, hour = 0, minute = 0, second = 0;
  unsigned char z = 0;

  if (!ret_time)
    return TRUE;
  memset(ret_time, 0, sizeof(*ret_time));

  /* Parse the time string */
  ret = sscanf(universal_time, "%02u%02u%02u%02u%02u%02u%c", &year, &month,
	       &day, &hour, &minute, &second, &z);
  if (ret < 3) {
    SILC_LOG_DEBUG(("Invalid UTC time string"));
    return FALSE;
  }

  /* Fill the SilcTime structure */
  ret = silc_time_fill(ret_time, year, month, day, hour, minute, second, 0);
  if (!ret) {
    SILC_LOG_DEBUG(("Incorrect values in UTC time string"));
    return FALSE;
  }

  /* Check timezone */
  if (z == '-' || z == '+') {
    ret = sscanf(universal_time + (ret * 2) + 1, "%02u%02u", &hour, &minute);
    if (ret != 2) {
      SILC_LOG_DEBUG(("Malformed UTC time string"));
      return FALSE;
    }

    if (hour > 23)
      return FALSE;
    if (minute > 60)
      return FALSE;

    ret_time->utc_hour   = hour;
    ret_time->utc_minute = minute;
    ret_time->utc_east   = (z == '-') ? 0 : 1;
  } else if (z != 'Z') {
    SILC_LOG_DEBUG(("Invalid timezone"));
    return FALSE;
  }

  /* UTC year must be fixed since it's represented only as YY not YYYY. */
  ret_time->year += 1900;
  if (ret_time->year < 1950)
    ret_time->year += 100;

  return TRUE;
}

/* Encode universal time string. */

SilcBool silc_time_universal_string(SilcTime time_val, char *ret_string,
				    SilcUInt32 ret_string_size)
{
  int ret, len = 0;
  memset(ret_string, 0, ret_string_size);
  ret = silc_snprintf(ret_string, ret_string_size - 1,
		 "%02u%02u%02u%02u%02u%02u",
		 time_val->year % 100, time_val->month, time_val->day,
		 time_val->hour, time_val->minute, time_val->second);
  if (ret < 0)
    return FALSE;
  len += ret;

  if (!time_val->utc_hour && !time_val->utc_minute) {
    ret = silc_snprintf(ret_string + len, ret_string_size - 1 - len, "Z");
    if (ret < 0)
      return FALSE;
    len += ret;
  } else {
    ret = silc_snprintf(ret_string + len, ret_string_size - 1 - len,
		   "%c%02u%02u", time_val->utc_east ? '+' : '-',
		   time_val->utc_hour, time_val->utc_minute);
    if (ret < 0)
      return FALSE;
    len += ret;
  }

  return TRUE;
}

/* Returns time from generalized time string into SilcTime */

SilcBool silc_time_generalized(const char *generalized_time, SilcTime ret_time)
{
  int ret, i;
  unsigned int year, month, day, hour = 0, minute = 0, second = 0;
  unsigned int msecond = 0;
  unsigned char z = 0;

  if (!ret_time)
    return TRUE;
  memset(ret_time, 0, sizeof(*ret_time));

  /* Parse the time string */
  ret = sscanf(generalized_time, "%04u%02u%02u%02u%02u%02u", &year, &month,
	       &day, &hour, &minute, &second);
  if (ret < 3) {
    SILC_LOG_DEBUG(("Invalid generalized time string"));
    return FALSE;
  }

  /* Fill the SilcTime structure */
  ret = silc_time_fill(ret_time, year, month, day, hour, minute, second, 0);
  if (!ret) {
    SILC_LOG_DEBUG(("Incorrect values in generalized time string"));
    return FALSE;
  }

  /* Check fractions of second and/or timezone */
  i = ret * 4;
  ret = sscanf(generalized_time + i, "%c", &z);
  if (ret != 1) {
    SILC_LOG_DEBUG(("Malformed generalized time string"));
    return FALSE;
  }

  if (z == '.') {
    /* Take fractions of second */
    int l;
    i++;
    ret = sscanf(generalized_time + i, "%u%n", &msecond, &l);
    if (ret != 1) {
      SILC_LOG_DEBUG(("Malformed generalized time string"));
      return FALSE;
    }
    while (l > 4) {
      msecond /= 10;
      l--;
    }
    ret_time->msecond = msecond;
    i += l;

    /* Read optional timezone */
    if (strlen(generalized_time) < i)
      sscanf(generalized_time + i, "%c", &z);
  }

  /* Check timezone if present */
  if (z == '-' || z == '+') {
    ret = sscanf(generalized_time + i + 1, "%02u%02u", &hour, &minute);
    if (ret != 2) {
      SILC_LOG_DEBUG(("Malformed UTC time string"));
      return FALSE;
    }

    if (hour > 23)
      return FALSE;
    if (minute > 60)
      return FALSE;

    ret_time->utc_hour   = hour;
    ret_time->utc_minute = minute;
    ret_time->utc_east   = (z == '-') ? 0 : 1;
  }

  return TRUE;
}

/* Encode generalized time string */

SilcBool silc_time_generalized_string(SilcTime time_val, char *ret_string,
				      SilcUInt32 ret_string_size)
{
  int len = 0, ret;
  memset(ret_string, 0, ret_string_size);
  ret = silc_snprintf(ret_string, ret_string_size - 1,
		 "%04u%02u%02u%02u%02u%02u",
		 time_val->year, time_val->month, time_val->day, time_val->hour,
		 time_val->minute, time_val->second);
  if (ret < 0)
    return FALSE;
  len += ret;

  if (time_val->msecond) {
    ret = silc_snprintf(ret_string + len, ret_string_size - 1 - len,
		   ".%lu", (unsigned long)time_val->msecond);
    if (ret < 0)
      return FALSE;
    len += ret;
  }

  if (!time_val->utc_hour && !time_val->utc_minute) {
    ret = silc_snprintf(ret_string + len, ret_string_size - 1 - len, "Z");
    if (ret < 0)
      return FALSE;
    len += ret;
  } else {
    ret = silc_snprintf(ret_string + len, ret_string_size - 1 - len,
		   "%c%02u%02u", time_val->utc_east ? '+' : '-',
		   time_val->utc_hour, time_val->utc_minute);
    if (ret < 0)
      return FALSE;
    len += ret;
  }

  return TRUE;
}

/* Return TRUE if `smaller' is smaller than `bigger'. */

int silc_compare_timeval(struct timeval *t1, struct timeval *t2)
{
  SilcInt32 s = t1->tv_sec - t2->tv_sec;
  if (!s)
    return t1->tv_usec - t2->tv_usec;
  return s;
}
