/************************************************************************/
/* Copyright 1990 - 1997 MBARI                                          */
/************************************************************************/
/* $Header: usrTime.c,v 1.4 94/02/18 11:41:40 hebo Exp $                */
/* Summary  : Unix-compatible Time Routines for SBE VPU-30 under vxWorks*/
/* Filename : usrTime.c                                                 */
/* Author   : Bob Herlien (rah)                                         */
/* Project  : New ROV                                                   */
/* $Revision: 1.4 $                                                     */
/* Created  : 02/28/91                                                  */
/************************************************************************/
/* Modification History:                                                */
/* $Log:        usrTime.c,v $
 * Revision 1.4  94/02/18  11:41:40  11:41:40  hebo (Bob Herlien)
 * Ported to VxWorks 5.1.1.  Added localtime_r() and gmtime_r().
 *
 * Revision 1.2  91/09/23  09:39:23  09:39:23  hebo (Bob Herlien 408-647-3748)
 * Minor bug fixes, added -DMICROTIME
 *
 * Revision 1.1  91/09/10  11:15:42  11:15:42  hebo (Bob Herlien 408-647-3748)
 * Moved RTC sync from task level to interrupt level, minor bug fixes, rah
 *
 * Revision 1.0  91/08/09  11:10:33  11:10:33  hebo (Bob Herlien 408-647-3748)
 * Initial revision
 *      */
/* 28feb91 rah, created                                                 */
/************************************************************************/
/* The Unix Epoch (day 0) is Jan 1, 1970.  The real-time clock chip     */
/* only gives 2 decimal digits of year.  Also, a Unix time_t value      */
/* (signed long) can only go +/- 68 years from 1970.  Therefore, these  */
/* routines assume that the year is between 1970 and 2038.  Note that   */
/* the leap-year exception for centuries not divisible by 400 is not a  */
/* factor in this period.                                               */
/************************************************************************/

#include <mbariTypes.h>                 /* MBARI standard types             */
#include <vxWorks.h>                    /* VxWorks types                    */
#include <time.h>                       /* struct tm definition             */
#include <sys/times.h>                  /* struct timeval, timezone defns   */
#include <usrTime.h>                    /* time structure definitions       */
#include <sysLib.h>                     /* VxWorks Driver defines           */
#include <stdio.h>                      /* VxWorks standard I/O library     */
#include <string.h>                     /* VxWorks string types             */
#include <envLib.h>                     /* Environment variable library     */
#include <limits.h>                     /* VxWorks integer limits           */

#ifdef SYNCRTC
#define __PROTOTYPE_5_0
#include <taskLib.h>                    /* For taskSpawn                    */
#define RTC_SYNC_PRIO   5               /* High priority but rarely runs    */
#define RTC_STACK_SIZE  2048
#define RTC_SYNC_TIME   3600            /* Sync once per hour               */
#endif

#define TZNAMELEN       32
#define LCLOFS          28800L          /* Offset in seconds from GMT to PST*/
#define LCLST           "PST"           /* Local timezone is PST            */
#define LCLDST          "PDT"           /* Local daylight savings zone is PDT*/
#define ADJTIME_MAX     ((INT_MAX / USECS_PER_SEC) - 1)
                                        /* Max seconds in adjtime call      */
#define CLKRATE         60              /* Default clock tick rate          */
#define CLK_GRANULARITY  1              /* Granularity of system clock in usec*/
                                        /* Used to round down # usecs/tick  */
                                        /* On a VPU-30, PIT clocks at 4 usec*/
                                        /* To defeat rounding, set to 1     */
                                        /* See usrTimeInit()                */

/********************************/
/*      External Functions      */
/********************************/

Extern void     usrClock();                          /* Normal clock tick   */
Extern time_t   sysRtcRead( struct tm *tp );         /* From sysRtc.c       */
Extern Void     sysRtcWrite( const struct tm *tp );  /* From sysRtc.c       */
Extern Void     sysMicroClockInit( Nat32 tick );     /* From sysMicroClock.c*/
Extern Void     sysMicroClockAdj( Nat32 tickTime );  /* From sysMicroClock.c*/
#ifdef MICROTIME
Extern Nat32    sysMicroClockGet();                  /* From sysMicroClock.c*/
Extern Void     sysMicroClockSet( Nat32 microTime ); /* From sysMicroClock.c*/
#endif


/********************************/
/*      Global Data             */
/********************************/

                /* Unix defined variables, per <time.h>                     */
Int32   timezone = LCLOFS;              /* Initialized for PST              */
char    *tzname[2] = { LCLST, LCLDST }; /* Local timezones: standard, daylight*/
#ifdef US_DST
Int32   daylight = 1;                   /* We're accounting for DST         */
#else
Int32   daylight = 0;                   /* We're not doing DST              */
#endif
                /* Following variables same name as on SUNOS. MUST BE POSITIVE*/
Nat32   hz = CLKRATE;                   /* Clock rate                        */
Nat32   tick = USECS_PER_SEC / CLKRATE; /* Microseconds per clock tick       */
Nat32   tickadj = 500 / CLKRATE;        /* Microsecs to speed up or slow down*/


/********************************/
/*      Module Local Data       */
/********************************/

MLocal Int32    adj_time;               /* Microseconds left to adjust      */
MLocal Nat32    currentTick;            /* Current adjusted tick            */
#if (defined(SET_TZ) && defined(US_DST) )
MLocal Int32    saveDst = 0;
#endif

MLocal Nat16    jdays[12] =             /* Days since Jan. 1 in non-leap yr */
{   0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334
};


/************************************************************************/
/* Function    : usrTimeTick                                            */
/* Purpose     : Clock tick handler for usrTime module                  */
/* Inputs      : None                                                   */
/* Outputs     : None                                                   */
/* Comments    : Called in interrupt context from Clk Int handler       */
/************************************************************************/
        Void
usrTimeTick( Void )
{
    Reg Int32   nextTickAdj;
    Reg Nat32   nextTick;

    nextTickAdj = adj_time;

    if ( adj_time >= 0 )
    {
        if ( adj_time > tickadj )
            nextTickAdj = tickadj;
    }
    else
    {
        if ( adj_time < -tickadj )
            nextTickAdj = -tickadj;
    }

    if ( (nextTick = tick + nextTickAdj) != currentTick )
    {
        sysMicroClockAdj( nextTick );
        currentTick = nextTick;
    }

    adj_time -= nextTickAdj;

    usrClock();                         /* Call "regular" clock tick func*/

} /* usrTimeTick() */


#ifdef SET_TZ
/************************************************************************/
/* Function    : setTzEnviron                                           */
/* Purpose     : Set TIMEZONE Environment variable from compiled-in info*/
/* Inputs      : Daylight savings time flag                             */
/* Outputs     : None                                                   */
/************************************************************************/
        Void
setTzEnviron( Int32 dst )
{
    Reg char    *zoneName;
    Int32       zoneOffset;
    char        envBuf[32];

    zoneOffset = timezone / 60;
    zoneName = tzname[0];

    if ( dst )
    {
        zoneOffset -= 60;
        zoneName = tzname[1];
    }

    sprintf( envBuf, "TIMEZONE=%s::%u", zoneName, (u_int)zoneOffset );
    putenv( envBuf );

} /* setTzEnviron() */
#endif


#ifdef US_DST
/************************************************************************/
/* Function    : isDst                                                  */
/* Purpose     : Determine whether we're in Daylight Savings Time (US method)*/
/* Inputs      : Current time                                           */
/* Outputs     : TRUE if DST, else FALSE                                */
/************************************************************************/
        Int32
isDst( time_t curTime )
{
    struct tm           tmBuf;

    localtime_r( &curTime, &tmBuf );    /* Convert time to struct tm*/

    if ( (tmBuf.tm_mon < 3) || (tmBuf.tm_mon > 9) )
        return( FALSE );                /* < April, > October is std time */

    if ( tmBuf.tm_mon == 3 )            /* DST starts 1st Sunday in April */
        return( (tmBuf.tm_mday > tmBuf.tm_wday) );

    if ( tmBuf.tm_mon == 9 )            /* DST ends last Sunday in October*/
        return( ((tmBuf.tm_mday - tmBuf.tm_wday) < 25) );

    return( TRUE );                     /* Summer months are DST          */

} /* isDst() */
#endif /* US_DST */


#ifdef SYNCRTC
/************************************************************************/
/* Function    : rtcSyncTask                                            */
/* Purpose     : Task to periodically write RTC from system time        */
/* Inputs      : None                                                   */
/* Outputs     : None                                                   */
/* Comments    : Runs forever                                           */
/************************************************************************/
        Void
rtcSyncTask( Void )
{
    int                 clkRate, ticksPastSecond;
    struct timeval      curTime;
    struct tm           tbuf;
    Int32               curDst;

    while( TRUE )
    {
        clkRate = sysClkRateGet();
        taskDelay( RTC_SYNC_TIME * clkRate );
                                                /* Default is once per hour*/
        gettimeofday( &curTime, (struct timezone *)NULL );
                                                /* Get current time        */
        ticksPastSecond = curTime.tv_usec * clkRate / USECS_PER_SEC;

        if ( ticksPastSecond > 2 )
        {                                       /* Wait for exact second   */
            taskDelay( clkRate - ticksPastSecond );
            curTime.tv_sec++;
        }

        gmtime_r( curTime.tv_sec, &tbuf );      /* Convert time to struct tm*/
        sysRtcWrite( &tbuf );                   /* Write RTC               */

#if (defined(SET_TZ) && defined(US_DST) )
        curDst = isDst( curTime.tv_sec );
        if ( curDst != saveDst )
            setTzEnviron( curDst );
        saveDst = curDst;
#endif
    }

} /* rtcSyncTask() */
#endif


/************************************************************************/
/* Function    : usrTimeInit                                            */
/* Purpose     : Initialize usrTime module                              */
/* Inputs      : None                                                   */
/* Outputs     : OK                                                     */
/* Comments    : tick is rounded down by CLK_GRANULARITY, to account for*/
/*               CPU boards whose timer chip is clocked slower than     */
/*               1 MHz (e.g. VPU-30)                                    */
/************************************************************************/
        STATUS
usrTimeInit( Void )
{
    struct tm           curTime;
    struct timespec     posixTime;

    hz = sysClkRateGet();
    tick = (USECS_PER_SEC / hz) / CLK_GRANULARITY * CLK_GRANULARITY;
    currentTick = tick;
    sysMicroClockInit( tick );

    posixTime.tv_sec = sysRtcRead( &curTime );
    posixTime.tv_nsec = 1000 * USECS_PER_SEC / 2; /* Best guess is .5 sec */
    clock_settime( CLOCK_REALTIME, &posixTime );

#ifdef SET_TZ
    saveDst = isDst( posixTime.tv_sec );
    setTzEnviron( saveDst );
#endif

    sysClkConnect( (FUNCPTR)usrTimeTick, 0 );

#ifdef SYNCRTC
    taskSpawn( "tRtc", RTC_SYNC_PRIO, VX_UNBREAKABLE, RTC_STACK_SIZE,
               (FUNCPTR)rtcSyncTask );
#endif

    return( OK );

} /* usrTimeInit() */


/************************************************************************/
/* Function    : adjtime                                                */
/* Purpose     : Clock tick handler for usrTime module                  */
/* Inputs      : Ptr to timval struct to adjust, ptr to amount left from*/
/*                last adjustment                                       */
/* Outputs     : Zero for OK, -1 if exceeds ADJTIME_MAX (2146 seconds)  */
/************************************************************************/
        int
adjtime( struct timeval *delta, struct timeval *olddelta )
{
    olddelta->tv_sec = (adj_time / USECS_PER_SEC);
    olddelta->tv_usec = (adj_time % USECS_PER_SEC);

    if ( (delta->tv_sec > ADJTIME_MAX) || (delta->tv_sec < -ADJTIME_MAX) )
        return( -1 );

    adj_time = (delta->tv_sec * USECS_PER_SEC) + delta->tv_usec;

    return( 0 );

} /* adjtime() */


/************************************************************************/
/* Function    : julday                                                 */
/* Purpose     : Calculate Julian Day given year, month, day            */
/* Inputs      : year (00 - 99), month (0 - 11), day (1 - 31)           */
/* Outputs     : Julian day                                             */
/* Comment     : Returns Julian day in range 1:366.  Unix wants 0:365   */
/************************************************************************/
        Int32
julday( Int32 yr, Int32 mo, Int32 day )
{
    Int32               leap;

    leap = (((yr % 4) == 0) && (mo > 1)) ? 1 : 0;
    return( jdays[mo] + day + leap );

} /* julday() */


/************************************************************************/
/* Function    : ep_days                                                */
/* Purpose     : Calculate number days since Unix epoch                 */
/* Inputs      : years since 1970, Unix-type julian day (0:365)         */
/* Outputs     : Days since 1/1/1970                                    */
/* Comment     : The (year + 1)/4 term accounts for leap years, the     */
/*               first of which was 1972 & should be added starting '73 */
/************************************************************************/
        Int32
ep_days( year, yday )
     int        year, yday;
{
    return( (365 * year) + ((year + 1)/4) + yday );

} /* ep_days() */


/************************************************************************/
/* Function    : getTimeZone                                            */
/* Purpose     : Get Time Zone name and offset from GMT                 */
/* Inputs      : Buffer to put name, buffer size                        */
/* Outputs     : Offset from GMT in minutes                             */
/************************************************************************/
        Int32
getTimeZone( char *name, Nat32 namesize )
{
    Reg char    *tzEnvName;
    Reg char    *p;
    Reg Nat32   tzLen;
    Int32       tzOffset;

    if ( (tzEnvName = getenv("TIMEZONE")) == NULL )
    {
        strncpy( name, tzname[0], namesize );
        return( tzOffset );
    }

    if ( (p = strchr(tzEnvName, ':')) != (char *) NULL )
    {
        tzLen = (p - tzEnvName);

        if ( ((p = strchr(p, ':')) == NULL) ||
             (sscanf(p, "%d", (int *)&tzOffset) <= 0) )
            tzOffset = timezone/60;
    }
    else
        tzLen = namesize-1;

    strncpy( name, tzEnvName, tzLen );
    name[tzLen] = '\0';

    return( tzOffset );

} /* getTimeZone() */


/************************************************************************/
/* Function    : stime                                                  */
/* Purpose     : Unix-compatible set time function                      */
/* Inputs      : Ptr to time in seconds from 1/1/70                     */
/* Outputs     : 0                                                      */
/************************************************************************/
        int
stime( long *tp )
{
    struct timespec     posixTime;
    struct tm           tbuf;

    posixTime.tv_sec = *tp;
    posixTime.tv_nsec = 0;
    clock_settime( CLOCK_REALTIME, &posixTime );
    gmtime_r( tp, &tbuf );                      /* Convert time to struct tm*/
    sysRtcWrite( &tbuf );                       /* Write RTC               */

#ifdef SET_TZ
    saveDst = isDst( posixTime.tv_sec );
    setTzEnviron( saveDst );
#endif

    return( 0 );

} /* stime() */


/************************************************************************/
/* Function    : gettimeofday                                           */
/* Purpose     : Unix-compatible function to get time in usecs          */
/* Inputs      : Ptr to struct timeval, ptr to struct timezone          */
/* Outputs     : 0                                                      */
/************************************************************************/
        int
gettimeofday( struct timeval *tp, struct timezone *tzp )
{
    struct timespec     currentTime;
    char                tzName[TZNAMELEN];

    clock_gettime( CLOCK_REALTIME, &currentTime );

    if ( tp != (struct timeval *)0 )
    {
        tp->tv_sec = currentTime.tv_sec;
        tp->tv_usec = currentTime.tv_nsec / 1000;

#ifdef MICROTIME
        tp->tv_usec += sysMicroClockGet();
        if ( tp->tv_usec >= USECS_PER_SEC )
        {
            tp->tv_usec -= USECS_PER_SEC;
            tp->tv_sec++;
        }
#endif
    }

    if ( tzp != (struct timezone *)0 )
    {
        tzp->tz_minuteswest = getTimeZone( tzName, TZNAMELEN );
        tzp->tz_dsttime = daylight;
    }
    return( 0 );

} /* gettimeofday() */


/************************************************************************/
/* Function    : settimeofday                                           */
/* Purpose     : Almost Unix-compatible function to set GMT time        */
/* Inputs      : Ptr to struct timeval to set, ptr to struct timezone to set*/
/* Outputs     : 0 if success, -1 if failure                            */
/* Comments    : Sets timezone offset and daylight savings flag         */
/************************************************************************/
        int
settimeofday( struct timeval *tp, struct timezone *tzp )
{
    struct timespec     posixTime;
    struct tm           tbuf;

    posixTime.tv_sec = tp->tv_sec;
    posixTime.tv_nsec = 1000 * tp->tv_usec;
#ifdef MICROTIME
    sysMicroClockSet(0);
#endif

    clock_settime( CLOCK_REALTIME, &posixTime );
    gmtime_r( &posixTime.tv_sec, &tbuf );       /* Convert time to struct tm*/
    sysRtcWrite( &tbuf );                       /* Write RTC               */

    if ( tzp != (struct timezone *)0 )
    {
        timezone = 60 * tzp->tz_minuteswest;
        daylight = tzp->tz_dsttime;
    }

#ifdef SET_TZ
    saveDst = isDst( tp->tv_sec );
    setTzEnviron( saveDst );
#endif

    return( 0 );

} /* settimeofday() */


/************************************************************************/
/* Function    : date                                                   */
/* Purpose     : User interface function to show time and date          */
/* Inputs      : None                                                   */
/* Outputs     : 0                                                      */
/* Comment     : Displays time and date to stdout                       */
/************************************************************************/
        Int32
date( Void )
{
    time_t      timer;
    struct tm   tmBuf;
    size_t      buflen;
    char        tzName[TZNAMELEN];
    char        ascTmBuf[48];

    time( &timer );
    gmtime_r( &timer, &tmBuf );
    buflen = sizeof(ascTmBuf);
    asctime_r( &tmBuf, ascTmBuf, &buflen );
    ascTmBuf[strlen(ascTmBuf)-1] = '\0';
    printf( "%s GMT\n", ascTmBuf );

    localtime_r( &timer, &tmBuf );
    buflen = sizeof(ascTmBuf);
    asctime_r( &tmBuf, ascTmBuf, &buflen );
    ascTmBuf[strlen(ascTmBuf)-1] = '\0';
    getTimeZone( tzName, TZNAMELEN );
    printf( "%s %s\n", ascTmBuf, tzName );

    return( 0 );

} /* date() */


/************************************************************************/
/* Function    : gmt_usage                                              */
/* Purpose     : Print usage message for gmtset                         */
/* Inputs      : Return code to return to user                          */
/* Outputs     : Return code                                            */
/************************************************************************/
        Int32
gmt_usage( Int32 rtn )
{
    printf("Usage: gmtset hhmmss[, yymmdd]\n");
    printf("\thhmmss = hours, minutes, seconds\n");
    printf("\tyymmdd = year, month, day\n");

    return( rtn );

} /* gmt_usage() */


/************************************************************************/
/* Function    : gmtset                                                 */
/* Purpose     : User interface function to set system time, date in GMT*/
/* Inputs      : Time in hhmmss format, (optional) date in yymmdd format*/
/* Outputs     : 0 if success, -1 if failure                            */
/* Comment     : System time is kept as GMT, not local time zone        */
/************************************************************************/
        Int32
gmtset( Int32 hhmmss, Int32 yymmdd )
{
    struct tm   *tp;
    time_t      curtime;
    Int32       rem;

    time( &curtime );                   /* Get current time             */
    tp = gmtime( &curtime );            /* Fill in *tp                  */

    if ( yymmdd )                       /* If date is present,          */
    {                                   /*   set date                   */
        tp->tm_year = yymmdd / 10000;   /* Get year field               */
        if ( tp->tm_year < YEAR0 )      /* If < 70, we're in 21st century*/
            tp->tm_year += 100;
        rem = yymmdd % 10000;
        tp->tm_mon = (rem / 100) - 1;   /* Get month                    */
        tp->tm_mday = rem % 100;        /* Get day                      */
        if ( (tp->tm_mon > 11) || (tp->tm_mday > 31) )
            return( gmt_usage(-1) );    /* Check for valid date         */
    }
    else if ( hhmmss == 0 )
        return( gmt_usage(-1) );        /* If no parms, print usage     */

    tp->tm_hour = hhmmss / 10000;       /* Get hour                     */
    rem = hhmmss % 10000;
    tp->tm_min = rem / 100;             /* Get minute                   */
    tp->tm_sec = rem % 100;             /* Get second                   */
    if ( (tp->tm_hour > 23) || (tp->tm_min > 59) || (tp->tm_sec > 59) )
        return( gmt_usage(-1) );        /* Check for valid time         */

    tp->tm_yday = julday(tp->tm_year, tp->tm_mon, tp->tm_mday) - 1;

    curtime = ep_days(tp->tm_year - YEAR0, tp->tm_yday) * SECS_PER_DAY;
    curtime += (tp->tm_hour * 3600L) + (tp->tm_min * 60) + tp->tm_sec;

    stime( &curtime );                  /* Write the time & date        */
    date();                             /* Print it                     */
    return( 0 );

} /* gmtset() */
