/*
 * SpanDSP - a series of DSP components for telephony
 *
 * timezone.c - Timezone handling for time interpretation
 *
 * Written by Steve Underwood <steveu@coppice.org>
 *
 * Copyright (C) 2010 Steve Underwood
 *
 * All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License version 2.1,
 * as published by the Free Software Foundation.
 *
 * 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

/*! \file */

/* Timezone processing might not seem like a DSP activity, but getting the headers
   right on FAXes demands it. We need to handle multiple time zones within a process,
   for FAXes related to different parts of the globe, so the system timezone handling
   is not adequate. */

/* This timezone handling is derived from public domain software by Arthur David Olson
   <arthur_david_olson@nih.gov> which you may download from ftp://elsie.nci.nih.gov/pub
   at the time of writing. */

#if defined(HAVE_CONFIG_H)
#include "config.h"
#endif

#include <stdlib.h>
#include <inttypes.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <stdlib.h>
#include <assert.h>

#include "spandsp/telephony.h"
#include "spandsp/timezone.h"

#include "spandsp/private/timezone.h"

#if !defined(FALSE)
#define FALSE    0
#endif

#if !defined(TRUE)
#define TRUE    (!FALSE)
#endif

#define SECS_PER_MIN            60
#define MINS_PER_HOUR           60
#define HOURS_PER_DAY           24
#define DAYS_PER_WEEK           7
#define DAYS_PER_NON_LEAP_YEAR  365
#define DAYS_PER_LEAP_YEAR      366
#define SECS_PER_HOUR           (SECS_PER_MIN*MINS_PER_HOUR)
#define SECS_PER_DAY            ((long int) SECS_PER_HOUR*HOURS_PER_DAY)
#define MONTHS_PER_YEAR         12

#define TM_YEAR_BASE            1900

#define EPOCH_YEAR              1970
#define EPOCH_WDAY              TM_THURSDAY

#define isleap(y)               (((y)%4) == 0  &&  (((y)%100) != 0  ||  ((y)%400) == 0))

#define isleap_sum(a, b)        isleap((a)%400 + (b)%400)

/* Unlike <ctype.h>'s isdigit, this also works if c < 0 | c > UCHAR_MAX. */
#define is_digit(c)             ((unsigned int) (c) - '0' <= 9)

#define TZ_DEF_RULE_STRING      ",M4.1.0,M10.5.0"

#define JULIAN_DAY              0       /* Jn - Julian day */
#define DAY_OF_YEAR             1       /* n - day of year */
#define MONTH_NTH_DAY_OF_WEEK   2       /* Mm.n.d - month, week, day of week */

static const char wildabbr[] = "   ";

static const char gmt[] = "GMT";

struct tz_rule_s
{
    int r_type;                         /* Type of rule--see below */
    int r_day;                          /* Day number of rule */
    int r_week;                         /* Week number of rule */
    int r_mon;                          /* Month number of rule */
    long int r_time;                    /* Transition time of rule */
};

static const int mon_lengths[2][MONTHS_PER_YEAR] =
{
    {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
    {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
};

static const int year_lengths[2] =
{
    DAYS_PER_NON_LEAP_YEAR,
    DAYS_PER_LEAP_YEAR
};

static int increment_overflow(int *number, int delta)
{
    int number0;

    number0 = *number;
    *number += delta;
    return (*number < number0) != (delta < 0);
}
/*- End of function --------------------------------------------------------*/

static void set_tzname(tz_t *tz)
{
    struct tz_state_s *sp;
    const struct tz_ttinfo_s *ttisp;
    int i;

    sp = &tz->state;
    tz->tzname[0] = wildabbr;
    tz->tzname[1] = wildabbr;
    for (i = 0;  i < sp->typecnt;  i++)
    {
        ttisp = &sp->ttis[i];
        tz->tzname[ttisp->isdst] = &sp->chars[ttisp->abbrind];
    }
    for (i = 0;  i < sp->timecnt;  i++)
    {
        ttisp = &sp->ttis[sp->types[i]];
        tz->tzname[ttisp->isdst] = &sp->chars[ttisp->abbrind];
    }
}
/*- End of function --------------------------------------------------------*/

/* Return the number of leap years through the end of the given year
   where, to make the math easy, the answer for year zero is defined as zero. */
static int leaps_thru_end_of(const int y)
{
    return (y >= 0)  ?  (y/4 - y/100 + y/400)  :  -(leaps_thru_end_of(-(y + 1)) + 1);
}
/*- End of function --------------------------------------------------------*/

static struct tm *time_sub(const time_t * const timep, const long int offset, const struct tz_state_s * const sp, struct tm * const tmp)
{
    const struct tz_lsinfo_s *lp;
    time_t tdays;
    const int *ip;
    int32_t corr;
    int32_t seconds;
    int32_t rem;
    int idays;
    int y;
    int hit;
    int i;

    corr = 0;
    hit = 0;
    i = sp->leapcnt;
    while (--i >= 0)
    {
        lp = &sp->lsis[i];
        if (*timep >= lp->trans)
        {
            if (*timep == lp->trans)
            {
                hit = ((i == 0  &&  lp->corr > 0)  ||  lp->corr > sp->lsis[i - 1].corr);
                if (hit)
                {
                    while (i > 0
                           &&
                           sp->lsis[i].trans == sp->lsis[i - 1].trans + 1
                           &&
                           sp->lsis[i].corr == sp->lsis[i - 1].corr + 1)
                    {
                        hit++;
                        --i;
                    }
                }
            }
            corr = lp->corr;
            break;
        }
    }
    y = EPOCH_YEAR;
    tdays = *timep/SECS_PER_DAY;
    rem = *timep - tdays*SECS_PER_DAY;
    while (tdays < 0  ||  tdays >= year_lengths[isleap(y)])
    {
        int newy;
        time_t tdelta;
        int idelta;
        int leapdays;

        tdelta = tdays / DAYS_PER_LEAP_YEAR;
        idelta = tdelta;
        if (tdelta - idelta >= 1  ||  idelta - tdelta >= 1)
            return NULL;
        if (idelta == 0)
            idelta = (tdays < 0)  ?  -1  :  1;
        newy = y;
        if (increment_overflow(&newy, idelta))
            return NULL;
        leapdays = leaps_thru_end_of(newy - 1) - leaps_thru_end_of(y - 1);
        tdays -= ((time_t) newy - y)*DAYS_PER_NON_LEAP_YEAR;
        tdays -= leapdays;
        y = newy;
    }
    seconds = tdays*SECS_PER_DAY;
    tdays = seconds/SECS_PER_DAY;
    rem += seconds - tdays*SECS_PER_DAY;
    /* Given the range, we can now fearlessly cast... */
    idays = tdays;
    rem += (offset - corr);
    while (rem < 0)
    {
        rem += SECS_PER_DAY;
        idays--;
    }
    while (rem >= SECS_PER_DAY)
    {
        rem -= SECS_PER_DAY;
        idays++;
    }
    while (idays < 0)
    {
        if (increment_overflow(&y, -1))
            return NULL;
        idays += year_lengths[isleap(y)];
    }
    while (idays >= year_lengths[isleap(y)])
    {
        idays -= year_lengths[isleap(y)];
        if (increment_overflow(&y, 1))
            return NULL;
    }
    tmp->tm_year = y;
    if (increment_overflow(&tmp->tm_year, -TM_YEAR_BASE))
        return NULL;
    tmp->tm_yday = idays;
    /* The "extra" mods below avoid overflow problems. */
    tmp->tm_wday = EPOCH_WDAY
                 + ((y - EPOCH_YEAR) % DAYS_PER_WEEK)*(DAYS_PER_NON_LEAP_YEAR % DAYS_PER_WEEK)
                 + leaps_thru_end_of(y - 1)
                 - leaps_thru_end_of(EPOCH_YEAR - 1)
                 + idays;
    tmp->tm_wday %= DAYS_PER_WEEK;
    if (tmp->tm_wday < 0)
        tmp->tm_wday += DAYS_PER_WEEK;
    tmp->tm_hour = (int) (rem/SECS_PER_HOUR);
    rem %= SECS_PER_HOUR;
    tmp->tm_min = (int) (rem/SECS_PER_MIN);
    /* A positive leap second requires a special
     * representation. This uses "... ??:59:60" et seq. */
    tmp->tm_sec = (int) (rem%SECS_PER_MIN) + hit;
    ip = mon_lengths[isleap(y)];
    for (tmp->tm_mon = 0;  idays >= ip[tmp->tm_mon];  (tmp->tm_mon)++)
        idays -= ip[tmp->tm_mon];
    tmp->tm_mday = (int) (idays + 1);
    tmp->tm_isdst = 0;
    return tmp;
}
/*- End of function --------------------------------------------------------*/

/* Given a pointer into a time zone string, scan until a character that is not
 * a valid character in a zone name is found. Return a pointer to that
 * character. */
static const char *get_tzname(const char *strp)
{
    char c;

    while ((c = *strp) != '\0'  &&  !is_digit(c)  &&  c != ','  &&  c != '-'  &&  c != '+')
        strp++;
    return strp;
}
/*- End of function --------------------------------------------------------*/

/* Given a pointer into a time zone string, extract a number from that string.
 * Check that the number is within a specified range; if it is not, return
 * NULL.
 * Otherwise, return a pointer to the first character not part of the number. */
static const char *get_num(const char *strp, int * const nump, const int min, const int max)
{
    char c;
    int num;

    if (strp == NULL  ||  !is_digit(c = *strp))
        return NULL;
    num = 0;
    do
    {
        num = num*10 + (c - '0');
        if (num > max)
            return NULL;    /* Illegal value */
        c = *++strp;
    }
    while (is_digit(c));
    if (num < min)
        return NULL;        /* Illegal value */
    *nump = num;
    return strp;
}
/*- End of function --------------------------------------------------------*/

/* Given a pointer into a time zone string, extract a number of seconds,
 * in hh[:mm[:ss]] form, from the string.
 * If any error occurs, return NULL.
 * Otherwise, return a pointer to the first character not part of the number
 * of seconds. */
static const char *get_secs(const char *strp, long int * const secsp)
{
    int num;

    /* HOURS_PER_DAY*DAYS_PER_WEEK - 1 allows quasi-Posix rules like
     * "M10.4.6/26", which does not conform to Posix,
     * but which specifies the equivalent of
     * "02:00 on the first Sunday on or after 23 Oct". */
    strp = get_num(strp, &num, 0, HOURS_PER_DAY*DAYS_PER_WEEK - 1);
    if (strp == NULL)
        return NULL;
    *secsp = num*(long int) SECS_PER_HOUR;
    if (*strp == ':')
    {
        strp = get_num(strp + 1, &num, 0, MINS_PER_HOUR - 1);
        if (strp == NULL)
            return NULL;
        *secsp += num*SECS_PER_MIN;
        if (*strp == ':')
        {
            /* SECS_PER_MIN allows for leap seconds. */
            strp = get_num(strp + 1, &num, 0, SECS_PER_MIN);
            if (strp == NULL)
                return NULL;
            *secsp += num;
        }
    }
    return strp;
}
/*- End of function --------------------------------------------------------*/

/* Given a pointer into a time zone string, extract an offset, in
 * [+-]hh[:mm[:ss]] form, from the string.
 * If any error occurs, return NULL.
 * Otherwise, return a pointer to the first character not part of the time. */
static const char *get_offset(const char *strp, long int * const offsetp)
{
    int neg = 0;

    if (*strp == '-')
    {
        neg = 1;
        strp++;
    }
    else if (*strp == '+')
    {
        strp++;
    }
    strp = get_secs(strp, offsetp);
    if (strp == NULL)
        return NULL;        /* Illegal time */
    if (neg)
        *offsetp = -*offsetp;
    return strp;
}
/*- End of function --------------------------------------------------------*/

/* Given a pointer into a time zone string, extract a rule in the form
 * date[/time]. See POSIX section 8 for the format of "date" and "time".
 * If a valid rule is not found, return NULL.
 * Otherwise, return a pointer to the first character not part of the rule. */
static const char *get_rule(const char *strp, struct tz_rule_s * const rulep)
{
    if (*strp == 'J')
    {
        /* Julian day. */
        rulep->r_type = JULIAN_DAY;
        strp = get_num(strp + 1, &rulep->r_day, 1, DAYS_PER_NON_LEAP_YEAR);
    }
    else if (*strp == 'M')
    {
        /* Month, week, day. */
        rulep->r_type = MONTH_NTH_DAY_OF_WEEK;
        strp = get_num(strp + 1, &rulep->r_mon, 1, MONTHS_PER_YEAR);
        if (strp == NULL  ||  *strp++ != '.')
            return NULL;
        strp = get_num(strp, &rulep->r_week, 1, 5);
        if (strp == NULL  ||  *strp++ != '.')
            return NULL;
        strp = get_num(strp, &rulep->r_day, 0, DAYS_PER_WEEK - 1);
    }
    else if (is_digit(*strp))
    {
        /* Day of the year. */
        rulep->r_type = DAY_OF_YEAR;
        strp = get_num(strp, &rulep->r_day, 0, DAYS_PER_LEAP_YEAR - 1);
    }
    else
    {
        /* Invalid format */
        return NULL;
    }
    if (strp == NULL)
        return NULL;
    if (*strp == '/')
    {
        /* Time specified. */
        strp = get_secs(strp + 1, &rulep->r_time);
    }
    else
    {
        /* Default = 2:00:00 */
        rulep->r_time = 2*SECS_PER_HOUR;
    }
    return strp;
}
/*- End of function --------------------------------------------------------*/

/* Given the Epoch-relative time of January 1, 00:00:00 UTC, in a year, the
 * year, a rule, and the offset from UTC at the time that rule takes effect,
 * calculate the Epoch-relative time that rule takes effect. */
static time_t trans_time(const time_t janfirst, const int year, const struct tz_rule_s * const rulep, const long int offset)
{
    int leapyear;
    time_t value;
    int i;
    int d;
    int m1;
    int yy0;
    int yy1;
    int yy2;
    int dow;

    value = 0;
    leapyear = isleap(year);
    switch (rulep->r_type)
    {
    case JULIAN_DAY:
        /* Jn - Julian day, 1 == January 1, 60 == March 1 even in leap
         * years.
         * In non-leap years, or if the day number is 59 or less, just
         * add SECS_PER_DAY times the day number-1 to the time of
         * January 1, midnight, to get the day. */
        value = janfirst + (rulep->r_day - 1)*SECS_PER_DAY;
        if (leapyear  &&  rulep->r_day >= 60)
            value += SECS_PER_DAY;
        break;
    case DAY_OF_YEAR:
        /* n - day of year.
         * Just add SECS_PER_DAY times the day number to the time of
         * January 1, midnight, to get the day. */
        value = janfirst + rulep->r_day * SECS_PER_DAY;
        break;
    case MONTH_NTH_DAY_OF_WEEK:
        /* Mm.n.d - nth "dth day" of month m. */
        value = janfirst;
        for (i = 0;  i < rulep->r_mon - 1;  i++)
            value += mon_lengths[leapyear][i]*SECS_PER_DAY;

        /* Use Zeller's Congruence to get day-of-week of first day of month. */
        m1 = (rulep->r_mon + 9)%12 + 1;
        yy0 = (rulep->r_mon <= 2)  ?  (year - 1)  :  year;
        yy1 = yy0/100;
        yy2 = yy0%100;
        dow = ((26*m1 - 2)/10 + 1 + yy2 + yy2/4 + yy1/4 - 2*yy1)%7;
        if (dow < 0)
            dow += DAYS_PER_WEEK;

        /* "dow" is the day-of-week of the first day of the month. Get
         * the day-of-month (zero-origin) of the first "dow" day of the
         * month. */
        d = rulep->r_day - dow;
        if (d < 0)
            d += DAYS_PER_WEEK;
        for (i = 1;  i < rulep->r_week;  i++)
        {
            if (d + DAYS_PER_WEEK >= mon_lengths[leapyear][rulep->r_mon - 1])
                break;
            d += DAYS_PER_WEEK;
        }

        /* "d" is the day-of-month (zero-origin) of the day we want. */
        value += d*SECS_PER_DAY;
        break;
    }

    /* "value" is the Epoch-relative time of 00:00:00 UTC on the day in
     * question. To get the Epoch-relative time of the specified local
     * time on that day, add the transition time and the current offset
     * from UTC. */
    return value + rulep->r_time + offset;
}
/*- End of function --------------------------------------------------------*/

/* Given a POSIX section 8-style TZ string, fill in the rule tables as
   appropriate. */
static int tzparse(const char *name, struct tz_state_s * const sp, const int lastditch)
{
    const char *stdname;
    const char *dstname;
    size_t stdlen;
    size_t dstlen;
    long int stdoffset;
    long int dstoffset;
    long int theirstdoffset;
    long int theirdstoffset;
    long int theiroffset;
    unsigned char *typep;
    char *cp;
    int load_result;
    int isdst;
    int i;
    int j;
    int year;
    struct tz_rule_s start;
    struct tz_rule_s end;
    time_t *atp;
    time_t janfirst;
    time_t starttime;
    time_t endtime;

    dstname = NULL;
    stdname = name;
    if (lastditch)
    {
        stdlen = strlen(name);      /* Length of standard zone name */
        name += stdlen;
        if (stdlen >= sizeof(sp->chars))
            stdlen = sizeof(sp->chars) - 1;
        stdoffset = 0;
    }
    else
    {
        name = get_tzname(name);
        stdlen = name - stdname;
        if (stdlen < 3)
            return -1;
        if (*name == '\0')
            return -1;
        name = get_offset(name, &stdoffset);
        if (name == NULL)
            return -1;
    }
    load_result = -1;
    if (load_result != 0)
        sp->leapcnt = 0;            /* So, we're off a little */
    if (*name != '\0')
    {
        dstname = name;
        name = get_tzname(name);
        dstlen = name - dstname;    /* Length of DST zone name */
        if (dstlen < 3)
            return -1;
        if (*name != '\0'  &&  *name != ','  &&  *name != ';')
        {
            if ((name = get_offset(name, &dstoffset)) == NULL)
                return -1;
        }
        else
        {
            dstoffset = stdoffset - SECS_PER_HOUR;
        }
        if (*name == '\0'  &&  load_result != 0)
            name = TZ_DEF_RULE_STRING;
        if (*name == ','  ||  *name == ';')
        {
            if ((name = get_rule(name + 1, &start)) == NULL)
                return -1;
            if (*name++ != ',')
                return -1;
            if ((name = get_rule(name, &end)) == NULL)
                return -1;
            if (*name != '\0')
                return -1;
            sp->typecnt = 2;        /* Standard time and DST */
            /* Two transitions per year, from EPOCH_YEAR to 2037. */
            sp->timecnt = 2*(2037 - EPOCH_YEAR + 1);
            if (sp->timecnt > TZ_MAX_TIMES)
                return -1;
            sp->ttis[0].gmtoff = -dstoffset;
            sp->ttis[0].isdst = 1;
            sp->ttis[0].abbrind = stdlen + 1;
            sp->ttis[1].gmtoff = -stdoffset;
            sp->ttis[1].isdst = 0;
            sp->ttis[1].abbrind = 0;
            atp = sp->ats;
            typep = sp->types;
            janfirst = 0;
            for (year = EPOCH_YEAR;  year <= 2037;  year++)
            {
                starttime = trans_time(janfirst, year, &start, stdoffset);
                endtime = trans_time(janfirst, year, &end, dstoffset);
                if (starttime > endtime)
                {
                    *atp++ = endtime;
                    *typep++ = 1;    /* DST ends */
                    *atp++ = starttime;
                    *typep++ = 0;    /* DST begins */
                }
                else
                {
                    *atp++ = starttime;
                    *typep++ = 0;    /* DST begins */
                    *atp++ = endtime;
                    *typep++ = 1;    /* DST ends */
                }
                janfirst += year_lengths[isleap(year)]*SECS_PER_DAY;
            }
        }
        else
        {
            if (*name != '\0')
                return -1;
            /* Initial values of theirstdoffset and theirdstoffset. */
            theirstdoffset = 0;
            for (i = 0;  i < sp->timecnt;  i++)
            {
                j = sp->types[i];
                if (!sp->ttis[j].isdst)
                {
                    theirstdoffset = -sp->ttis[j].gmtoff;
                    break;
                }
            }
            theirdstoffset = 0;
            for (i = 0;  i < sp->timecnt;  i++)
            {
                j = sp->types[i];
                if (sp->ttis[j].isdst)
                {
                    theirdstoffset = -sp->ttis[j].gmtoff;
                    break;
                }
            }
            /* Initially we're assumed to be in standard time. */
            isdst = FALSE;
            theiroffset = theirstdoffset;
            /* Now juggle transition times and types tracking offsets as you do. */
            for (i = 0;  i < sp->timecnt;  i++)
            {
                j = sp->types[i];
                sp->types[i] = sp->ttis[j].isdst;
                if (sp->ttis[j].ttisgmt)
                {
                    /* No adjustment to transition time */
                }
                else
                {
                    /* If summer time is in effect, and the
                     * transition time was not specified as
                     * standard time, add the summer time
                     * offset to the transition time;
                     * otherwise, add the standard time
                     * offset to the transition time. */
                    /* Transitions from DST to DDST
                     * will effectively disappear since
                     * POSIX provides for only one DST
                     * offset. */
                    if (isdst  &&  !sp->ttis[j].ttisstd)
                        sp->ats[i] += (dstoffset - theirdstoffset);
                    else
                        sp->ats[i] += (stdoffset - theirstdoffset);
                }
                theiroffset = -sp->ttis[j].gmtoff;
                if (sp->ttis[j].isdst)
                    theirdstoffset = theiroffset;
                else
                    theirstdoffset = theiroffset;
            }
            /* Finally, fill in ttis. ttisstd and ttisgmt need not be handled. */
            sp->ttis[0].gmtoff = -stdoffset;
            sp->ttis[0].isdst = FALSE;
            sp->ttis[0].abbrind = 0;
            sp->ttis[1].gmtoff = -dstoffset;
            sp->ttis[1].isdst = TRUE;
            sp->ttis[1].abbrind = stdlen + 1;
            sp->typecnt = 2;
        }
    }
    else
    {
        dstlen = 0;
        sp->typecnt = 1;        /* Only standard time */
        sp->timecnt = 0;
        sp->ttis[0].gmtoff = -stdoffset;
        sp->ttis[0].isdst = 0;
        sp->ttis[0].abbrind = 0;
    }
    sp->charcnt = stdlen + 1;
    if (dstlen != 0)
        sp->charcnt += dstlen + 1;
    if ((size_t) sp->charcnt > sizeof(sp->chars))
        return -1;
    cp = sp->chars;
    strncpy(cp, stdname, stdlen);
    cp += stdlen;
    *cp++ = '\0';
    if (dstlen != 0)
    {
        strncpy(cp, dstname, dstlen);
        cp[dstlen] = '\0';
    }
    return 0;
}
/*- End of function --------------------------------------------------------*/

static void tz_set(tz_t *tz, const char *tzstring)
{
    const char *name = "";
    struct tz_state_s *lclptr = &tz->state;

    if (tzstring)
        name = tzstring;

    /* See if we are already set OK */
    if (tz->lcl_is_set > 0  &&  strcmp(tz->lcl_tzname, name) == 0)
        return;
    tz->lcl_is_set = strlen(name) < sizeof(tz->lcl_tzname);
    if (tz->lcl_is_set)
        strcpy(tz->lcl_tzname, name);

    if (name[0] == '\0')
    {
        /* User wants it fast rather than right, so, we're off a little. */
        lclptr->leapcnt = 0;
        lclptr->timecnt = 0;
        lclptr->typecnt = 0;
        lclptr->ttis[0].isdst = 0;
        lclptr->ttis[0].gmtoff = 0;
        lclptr->ttis[0].abbrind = 0;
        strcpy(lclptr->chars, gmt);
    }
    else if (name[0] == ':'  ||  tzparse(name, lclptr, FALSE) != 0)
    {
        tzparse(gmt, lclptr, TRUE);
    }
    set_tzname(tz);
}
/*- End of function --------------------------------------------------------*/

SPAN_DECLARE(int) tz_localtime(tz_t *tz, struct tm *tmp, time_t t)
{
    struct tz_state_s *sp;
    const struct tz_ttinfo_s *ttisp;
    int i;

    sp = &tz->state;

    if (sp->timecnt == 0  ||  t < sp->ats[0])
    {
        i = 0;
        while (sp->ttis[i].isdst)
        {
            if (++i >= sp->typecnt)
            {
                i = 0;
                break;
            }
        }
    }
    else
    {
        for (i = 1;  i < sp->timecnt;  i++)
        {
            if (t < sp->ats[i])
                break;
        }
        i = (int) sp->types[i - 1];
    }
    ttisp = &sp->ttis[i];
    time_sub(&t, ttisp->gmtoff, sp, tmp);
    tmp->tm_isdst = ttisp->isdst;
    tz->tzname[tmp->tm_isdst] = &sp->chars[ttisp->abbrind];
    return 0;
}
/*- End of function --------------------------------------------------------*/

SPAN_DECLARE(const char *) tz_tzname(tz_t *tz, int isdst)
{
    return tz->tzname[(!isdst)  ?  0  :  1];
}
/*- End of function --------------------------------------------------------*/

SPAN_DECLARE(tz_t *) tz_init(tz_t *tz, const char *tzstring)
{
    if (tz == NULL)
    {
        if ((tz = (tz_t *) malloc(sizeof(*tz))) == NULL)
            return NULL;
    }
    memset(tz, 0, sizeof(*tz));
    tz->tzname[0] =
    tz->tzname[1] = wildabbr;
    tz_set(tz, tzstring);
    return tz;
}
/*- End of function --------------------------------------------------------*/

SPAN_DECLARE(int) tz_release(tz_t *tz)
{
    return 0;
}
/*- End of function --------------------------------------------------------*/

SPAN_DECLARE(int) tz_free(tz_t *tz)
{
    if (tz)
        free(tz);
    return 0;
}
/*- End of function --------------------------------------------------------*/
/*- End of file ------------------------------------------------------------*/