/*
** Copyright (C) 1994, 1995 Enterprise Integration Technologies Corp.
**         VeriFone Inc./Hewlett-Packard. All Rights Reserved.
** Kevin Hughes, kev@kevcom.com 3/11/94
** Kent Landfield, kent@landfield.com 4/6/97
**
** This program and library is free software; you can redistribute it and/or
** modify it under the terms of the GNU (Library) General Public License
** as published by the Free Software Foundation; either version 2
** of the License, or any later version.
**
** 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 (Library) General Public License for more details.
**
** You should have received a copy of the GNU (Library) General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA 
*/

/*
** Move to wrappers calling standard functions - perhaps slower
** but easier to maintain
*/

#include "hypermail.h"
#include "setup.h"

/* 
** Given a long date string, it returns the number of seconds
** since BASEYEAR. (Y2K ok)
*/

extern time_t get_date(const char *, const time_t *);

static time_t ydhms_tm_diff(int, int, int, int, int, const struct tm *);
static time_t my_mktime(struct tm *);

time_t convtoyearsecs(char *date)
{
    time_t yearsecs;
    char *p, *s = date;

    /* the (non-standard) timezone specs GMT0 and BST-1
     * confuse the get_date routines (GMT0 sets the year to 0).
     * Rather than altering the standard routine, we try to
     * put them into a standard format here.
     */
    if ((p = strstr(date, "GMT0")) != NULL) {
	s = malloc(strlen(date) + 1);
	strcpy(s, date);
	*(s + (p - date) + 3) = '\0';
    }
    else if ((p = strstr(date, "BST-1")) != NULL) {
	s = malloc(strlen(date) + 1);
	strcpy(s, date);
	strcpy(s + (p - date), "-1");
    }

    yearsecs = get_date(s, (time_t *)NULL);
    if (s != date) {
	free(s);
    }

    /* if we can't parse the date string, the calling
     * routine needs to know as it may have another
     * way of getting the date.
     */
    return yearsecs;
}

/* 
** Gets the local time and returns it formatted.
*/

char *getlocaltime(void)
{
    static char s[DATESTRLEN + 5];
    time_t tp;
    struct tm *tmptr;

    time(&tp);
    tmptr = (set_gmtime ? gmtime(&tp) : localtime(&tp));

    s[0] = '\0';

    if (set_dateformat != NULL) {
	strftime(s, DATESTRLEN, set_dateformat, tmptr);
        /* 
	 * Need to check if the timezone %Z was specified. 
	 * If so do not print it out automatically.
	 */
       if (strstr(set_dateformat, "%Z") == NULL)
           sprintf(s, "%s %s", s, timezonestr);
    }
    else {
	if (set_eurodate)
	    strftime(s, DATESTRLEN, "%a %d %b %Y - %H:%M:%S", tmptr);
	else if (set_isodate) {
	    if (set_gmtime)
	        strftime(s, DATESTRLEN, "%Y-%m-%dZ%H:%M:%S", tmptr);
	    else
	        strftime(s, DATESTRLEN, "%Y-%m-%d %H:%M:%S", tmptr);
	}
	else
	    strftime(s, DATESTRLEN, "%a %b %d %Y - %H:%M:%S", tmptr);

        sprintf(s, "%s %s", s, timezonestr);
    }
    return s;
}

/* 
** Gets the local time zone.
*/

void gettimezone(void)
{
    time_t tp;

    time(&tp);
    strftime(timezonestr, TIMEZONELEN, "%Z",
	     set_gmtime ? gmtime(&tp) : localtime(&tp));
}

/*
** Gets the current year.
*/

void getthisyear(void)
{
    time_t tp;

    time(&tp);
    strftime(thisyear, YEARLEN, "%Y", set_gmtime ? gmtime(&tp) : localtime(&tp));
}

int year_of_datenum(time_t t)
{
  struct tm *tptr = (set_gmtime ? gmtime(&t) : localtime(&t));
  return tptr->tm_year + 1900;
}

int month_of_datenum(time_t t)
{
  struct tm *tptr = (set_gmtime ? gmtime(&t) : localtime(&t));
  return tptr->tm_mon;
}

/* 
** From the number of seconds since BASEYEAR, this pretty-prints
** a date for you. 
*/

char *getdatestr(time_t yearsecs)
{
    static char date[DATESTRLEN];
    struct tm *tmptr = (set_gmtime ? gmtime(&yearsecs) : localtime(&yearsecs));

    if (set_dateformat != NULL) {
	strftime(date, DATESTRLEN, set_dateformat, tmptr);
    }
    else {
	if (set_eurodate)
	    strftime(date, DATESTRLEN, "%a %d %b %Y - %H:%M:%S %Z", tmptr);
	else if (set_isodate) {
	    if (set_gmtime)
	        strftime(date, DATESTRLEN, "%Y-%m-%dZ%H:%M:%S", tmptr);
	    else
	        strftime(date, DATESTRLEN, "%Y-%m-%d %H:%M:%S", tmptr);
	}
	else
	    strftime(date, DATESTRLEN, "%a %b %d %Y - %H:%M:%S %Z", tmptr);
    }
    return date;
}

/* 
** This function calls getdatestr, but with an alternate date format
** that is used for showing dates in the indexs
*/

char *getindexdatestr(time_t yearsecs)
{
  char *previous_dateformat;
  char *date;

  /* store the previous dateformat */
  previous_dateformat = set_dateformat;
  /* if there's an index date format, we use it, otherwise use the
   standard  dateformat */
  if (set_indexdateformat)
    set_dateformat = set_indexdateformat;
  date = getdatestr (yearsecs);
  /* restore the previous dateformat */
  set_dateformat = previous_dateformat;
  return date;
}

/* 
** This function calls getdatestr, but with a fixed  alternate
**  date format that we use for comparing two dates minus the hour.
*/

char *getdateindexdatestr(time_t yearsecs)
{
  char *previous_dateformat;
  char *date;

  /* store the previous dateformat */
  previous_dateformat = set_dateformat;
  set_dateformat = "%A, %e %B %Y";
  date = getdatestr (yearsecs);
  /* restore the previous dateformat */
  set_dateformat = previous_dateformat;
  return date;
}

char *secs_to_iso(time_t t)
{
    /* is passed time_t variable
     * holding number of seconds since EPOCH
     * returns pointer to string holding date in format
     * YYYYMMDDHHMMSS
     * This buffer will be overwritten by next call to secs_to_iso.
     */
    static char s[15];
    struct tm *tm;

    tm = gmtime(&t);
    sprintf(s, "%4.4d%02.2d%02.2d%02.2d%02.2d%02.2d",
	    tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday,
	    tm->tm_hour, tm->tm_min, tm->tm_sec);
    return s;
}

char *secs_to_iso_meta(time_t t)
{
    /* is passed time_t variable
     * holding number of seconds since EPOCH
     * returns pointer to string holding date in format
     * YYYY-MM-DD
     * This buffer will be overwritten by next call to secs_to_iso_meta.
     */
    static char s[11];
    struct tm *tm;

    tm = localtime(&t);
    sprintf(s, "%4.4d-%02.2d-%02.2d",
	    tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
    return s;
}

time_t iso_to_secs(char *isodate)
{
    /* is passed string holding date in format
     * YYYYMMDDHHMMSS
     * returns number of seconds since EPOCH
     */
    struct tm t;
    char s[15];

    strncpy(s, isodate, 14);
    s[14] = '\0';
    t.tm_isdst = 0;
    t.tm_yday = 0;
    t.tm_wday = 0;
    t.tm_sec = atoi(s + 12);
    *(s + 12) = '\0';
    t.tm_min = atoi(s + 10);
    *(s + 10) = '\0';
    t.tm_hour = atoi(s + 8);
    *(s + 8) = '\0';
    t.tm_mday = atoi(s + 6);
    *(s + 6) = '\0';
    t.tm_mon = atoi(s + 4) - 1;
    *(s + 4) = '\0';
    t.tm_year = atoi(s) - 1900;

    return my_mktime(&t);
}


/* Based on mktime.c from the GNU C library (glibc-2.0.6)
 * simplified to ignore timezones completely for use by hypermail when 
 * converting from iso string representation of time to seconds since
 * epoch (interpreting given time as UTC).
 *
 * Paul Haldane
 */

#define TM_YEAR_BASE 1900
#define EPOCH_YEAR 1970

#ifndef __isleap
/* Nonzero if YEAR is a leap year (every 4 years,
   except every 100th isn't, and every 400th is).  */
# define __isleap(year)	\
  ((year) % 4 == 0 && ((year) % 100 != 0 || (year) % 400 == 0))
#endif

/* How many days come before each month (0-12).  */
const unsigned short int __mon_yday[2][13] = {
    /* Normal years.  */
    {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365},
    /* Leap years.  */
    {0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366}
};

/* Yield the difference between (YEAR-YDAY HOUR:MIN:SEC) and (*TP),
   measured in seconds, ignoring leap seconds.
   YEAR uses the same numbering as TM->tm_year.
   All values are in range, except possibly YEAR.
   If overflow occurs, yield the low order bits of the correct answer.  */

static time_t ydhms_tm_diff(int year, int yday, int hour,
			    int min, int sec, const struct tm *tp)
{
    /* Compute intervening leap days correctly even if year is negative.
       Take care to avoid int overflow.  time_t overflow is OK, since
       only the low order bits of the correct time_t answer are needed.
       Don't convert to time_t until after all divisions are done, since
       time_t might be unsigned.  */
    int a4 = (year >> 2) + (TM_YEAR_BASE >> 2) - !(year & 3);
    int b4 = (tp->tm_year >> 2) + (TM_YEAR_BASE >> 2) - !(tp->tm_year & 3);
    int a100 = a4 / 25 - (a4 % 25 < 0);
    int b100 = b4 / 25 - (b4 % 25 < 0);
    int a400 = a100 >> 2;
    int b400 = b100 >> 2;
    int intervening_leap_days = (a4 - b4) - (a100 - b100) + (a400 - b400);
    time_t years = year - (time_t)tp->tm_year;
    time_t ddays = (365 * years + intervening_leap_days + (yday - tp->tm_yday));
    return (60 * (60 * (24 * ddays + (hour - tp->tm_hour))
		  + (min - tp->tm_min)) + (sec - tp->tm_sec));
}

/* Convert *TP to a time_t value */

static time_t my_mktime(struct tm *tp)
{
    time_t t0;
    struct tm tm;

    /* Time requested.  Copy it in case CONVERT modifies *TP; this can
       occur if TP is localtime's returned value and CONVERT is localtime.  */
    int sec = tp->tm_sec;
    int min = tp->tm_min;
    int hour = tp->tm_hour;
    int mday = tp->tm_mday;
    int mon = tp->tm_mon;
    int year_requested = tp->tm_year;

    /* Ensure that mon is in range, and set year accordingly.  */
    int mon_remainder = mon % 12;
    int negative_mon_remainder = mon_remainder < 0;
    int mon_years = mon / 12 - negative_mon_remainder;
    int year = year_requested + mon_years;

    /* The other values need not be in range:
       the remaining code handles minor overflows correctly,
       assuming int and time_t arithmetic wraps around.
       Major overflows are caught at the end.  */

    /* Calculate day of year from year, month, and day of month.
       The result need not be in range.  */
    int yday = ((__mon_yday[__isleap(year + TM_YEAR_BASE)]
		 [mon_remainder + 12 * negative_mon_remainder])
		+ mday - 1);

    tm.tm_year = EPOCH_YEAR - TM_YEAR_BASE;
    tm.tm_yday = tm.tm_hour = tm.tm_min = tm.tm_sec = 0;
    t0 = ydhms_tm_diff(year, yday, hour, min, sec, &tm);

    return t0;
}
