/****************************************************************************
*
*                            Open Watcom Project
*
*    Portions Copyright (c) 1983-2002 Sybase, Inc. All Rights Reserved.
*
*  ========================================================================
*
*    This file contains Original Code and/or Modifications of Original
*    Code as defined in and that are subject to the Sybase Open Watcom
*    Public License version 1.0 (the 'License'). You may not use this file
*    except in compliance with the License. BY USING THIS FILE YOU AGREE TO
*    ALL TERMS AND CONDITIONS OF THE LICENSE. A copy of the License is
*    provided with the Original Code and Modifications, and is also
*    available at www.sybase.com/developer/opensource.
*
*    The Original Code and all software distributed under the License are
*    distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
*    EXPRESS OR IMPLIED, AND SYBASE AND ALL CONTRIBUTORS HEREBY DISCLAIM
*    ALL SUCH WARRANTIES, INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF
*    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR
*    NON-INFRINGEMENT. Please see the License for the specific language
*    governing rights and limitations under the License.
*
*  ========================================================================
*
* Description:  time utility functions
*
****************************************************************************/

#include "variety.h"
#include <time.h>
#include "rtdata.h"
#include "timedata.h"

static int time_less( const struct tm *t1, const struct tm *t2 );

static int calc_yday( const struct tm *timetm, int year )
{
    struct tm   tmptm;
    int         month_days;
    int         first_wday;
    int         nth_week;
    short const *diyr;

    if( timetm->tm_isdst == 0 ) { // M.m.n.d form
        diyr = ( __leapyear( ( unsigned ) year + 1900 ) ) ? __dilyr : __diyr;
        month_days = diyr[timetm->tm_mon + 1] - diyr[timetm->tm_mon]; 
        tmptm.tm_sec   = 0;
        tmptm.tm_min   = 0;
        tmptm.tm_hour  = 0;
        tmptm.tm_mday  = 1;
        tmptm.tm_mon   = timetm->tm_mon;
        tmptm.tm_year  = year;
        tmptm.tm_isdst = 0;
        ( void ) mktime( &tmptm );
        first_wday = ( timetm->tm_wday - tmptm.tm_wday + 7 ) % 7;
        if( timetm->tm_mday == 5 ) {
            if( ( 1 + first_wday + ( timetm->tm_mday - 1 ) * 7 ) > month_days )
                nth_week = timetm->tm_mday - 2;   // fifth req. weekday does not exist
            else 
                nth_week = timetm->tm_mday - 1;
        } else 
            nth_week = timetm->tm_mday - 1;
        return( tmptm.tm_yday + first_wday + nth_week * 7 );
    }
    if( timetm->tm_isdst == 1 )  /* if Jn form */
        return( timetm->tm_yday - 1 );
    return( timetm->tm_yday );
}

/* determine if in souther hemisphere -> start is after end */
static int check_order( const struct tm *start, const struct tm *end, int year )
{
    int start_day;
    int end_day;

    /* these quick checks should always be enough */
    if( ( start->tm_isdst == 0 ) && ( end->tm_isdst == 0 ) ) { // M.m.n.d form
        if( start->tm_mon > end->tm_mon ) 
            return( 1 ); 
        if( start->tm_mon < end->tm_mon ) 
            return( 0 );
    }
    /* start/end of daylight savings time is in the same month (rare case) */
    /* these are *expensive* calculations under NT since 2 TZ checks must be done */
    start_day = calc_yday( start, year );
    end_day = calc_yday( end, year );
    if( start_day > end_day ) 
        return( 1 );
    return( 0 );
}

/* determine if daylight savings time */
int __isindst( struct tm *t )
{
    int                 month;
    int                 dst;
    int                 n1;
    int                 n2;
    int                 month_days;
    int                 time_check;
    int                 south;
    struct tm const     *start;
    struct tm const     *end;
    short const         *diyr;

    // already determined -- if we are sure
    if( t->tm_isdst >= 0 ) 
        return( t->tm_isdst );
    dst = 0;
    // if zone doesn't have a daylight savings period
    if( _RWD_daylight == 0 ) 
        return( t->tm_isdst = dst );
    //  // check for no daylight savings time rule
    //  if( tzname[1][0] == '\0' ) {    // doesn't work since Win32 says
    //      return( t->tm_isdst = dst );// daylight zone name = standard zone name
    //  }

    south = check_order( &_RWD_start_dst, &_RWD_end_dst, t->tm_year );
    if( south ) {
        // if southern hemisphere
        // invert start and end dates and then invert return value
        start = &_RWD_end_dst;
        end = &_RWD_start_dst;
    } else {
        start = &_RWD_start_dst;
        end = &_RWD_end_dst;
    }
    month = t->tm_mon;
    diyr = ( __leapyear( ( unsigned ) t->tm_year + 1900 ) ) ? __dilyr : __diyr;
    month_days = diyr[month + 1] - diyr[month]; 
    time_check = 0;
    /*
     * M.m.n.d form
     * m = start->tm_mon  (month 0-11)
     * n = start->tm_mday (n'th week day 1-5)
     * d = start->tm_wday (week day 0-6)
     */
    if( start->tm_isdst == 0 ) { /* if Mm.n.d form */
        if( month > start->tm_mon ) 
            dst = 1;                        /* assume dst for now */ 
        else if( month == start->tm_mon ) {
            /* calculate for current day */
            n1 = t->tm_mday - ( t->tm_wday + 7 - start->tm_wday ) % 7;
            /* calculate for previous day */
            n2 = t->tm_mday - 1 - ( t->tm_wday - 1 + 7 - start->tm_wday ) % 7;
            //  n_ stands for the day of the month that is past &&
            //  is closest to today && is the required weekday
            if( start->tm_mday == 5 ) {
                if( n1 > month_days - 7 ) {
                    dst = 1;                /* assume dst for now */
                    if( n2 <= month_days - 7 ) 
                        time_check = 1;
                }
            } else {
                if( n1 >= 7 * ( start->tm_mday - 1 ) + 1 ) {
                    dst = 1;                /* assume dst for now */
                    if( n2 < 7 * ( start->tm_mday - 1 ) + 1 ) 
                        time_check = 1;
                }
            }
        }
    } else {
        n1 = start->tm_yday;
        if( start->tm_isdst == 1 ) { /* if Jn form */
            if( __leapyear( ( unsigned ) t->tm_year + 1900 ) ) {
                if( n1 > __diyr[2] )
                    n1++;      /* past Feb 28 */
            }
            n1--;
        }
        if( t->tm_yday >= n1 ) {
            dst = 1;                        /* assume dst for now */
            if( t->tm_yday == n1 )
                time_check = 1;
        }
    }
    /* if it is the day for a switch-over then check the time too */
    if( time_check )
        dst = !time_less( t, start );

    /* if we are certain that it is before daylight saving then return */
    if( dst == 0 ) {
        if( south )
            dst = south - dst;  /* invert value of dst */
        return( t->tm_isdst = dst );
    }

    /* now see if it is after daylight saving */
    time_check = 0;
    if( end->tm_isdst == 0 ) { /* if Mm.n.d form */
        if( month > end->tm_mon ) 
            dst = 0;                        /* not dst */ 
        else if( month == end->tm_mon ) {
            dst = 0;
            /* calculate for current day */
            n1 = t->tm_mday - ( t->tm_wday + 7 - end->tm_wday ) % 7;
            /* calculate for previous day */
            n2 = t->tm_mday - 1 -
                ( t->tm_wday - 1 + 7 - end->tm_wday ) % 7;
            if( end->tm_mday == 5 ) {
                if( n1 <= month_days - 7 ) 
                    dst = 1; 
                else if( n2 <= month_days - 7 ) 
                    time_check = 1;
            } else {
                if( n1 < 7 * ( end->tm_mday - 1 ) + 1 ) 
                    dst = 1; 
                else if( n2 < 7 * ( end->tm_mday - 1 ) + 1 ) 
                    time_check = 1;
            }
        }
    } else {
        n1 = end->tm_yday;
        if( end->tm_isdst == 1 ) { /* if Jn form */
            if( __leapyear( ( unsigned ) t->tm_year + 1900 ) ) {
                if( n1 > __diyr[2] )
                    n1++;      /* past Feb 28 */
            }
            n1--;
        }
        if( t->tm_yday >= n1 ) {
            dst = 0;
            if( t->tm_yday == n1 )
                time_check = 1;
        }
    }
    /* if it is the day for a switch-over then check the time too */
    if( time_check )
        dst = time_less( t, end );
    if( south )
        dst = south - dst;      /* invert value of dst */
    return( t->tm_isdst = dst );
}

static int time_less( const struct tm *t1, const struct tm *t2 )
{
    int before;

    before = 0;
    if( t1->tm_hour < t2->tm_hour ) 
        before = 1; 
    else if( t1->tm_hour == t2->tm_hour ) {
        if( t1->tm_min < t2->tm_min
        ||  t1->tm_min == t2->tm_min && t1->tm_sec < t2->tm_sec )
                before = 1;
    }
    return( before );
}