/* 
 * tclMacTime.c --
 *
 *	Contains Macintosh specific versions of Tcl functions that
 *	obtain time values from the operating system.
 *
 * Copyright (c) 1995-1997 Sun Microsystems, Inc.
 *
 * See the file "license.terms" for information on usage and redistribution
 * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
 *
 * RCS: @(#) $Id: tclMacTime.c,v 1.7 2002/01/04 11:21:05 das Exp $
 */

#include "tclInt.h"
#include "tclPort.h"
#include "tclMacInt.h"
#include <OSUtils.h>
#include <Timer.h>
#include <time.h>

/*
 * Static variables used by the Tcl_GetTime function.
 */
 
static int initalized = false;
static unsigned long baseSeconds;
static UnsignedWide microOffset;

static int gmt_initialized = false;
static long gmt_offset;
static int gmt_isdst;
TCL_DECLARE_MUTEX(gmtMutex)

static int gmt_lastGetDateUseGMT = 0;

typedef struct _TABLE {
    char        *name;
    int         type;
    time_t      value;
} TABLE;


#define HOUR(x)         ((time_t) (3600 * x))

#define tZONE 0
#define tDAYZONE 1


/*
 * inverse timezone table, adapted from tclDate.c by removing duplicates and
 * adding some made up names for unusual daylight savings
 */
static TABLE    invTimezoneTable[] = {
    { "Z",    -1,     HOUR( 36) },      /* Unknown */
    { "GMT",    tZONE,     HOUR( 0) },      /* Greenwich Mean */
    { "BST",    tDAYZONE,  HOUR( 0) },      /* British Summer */
    { "WAT",    tZONE,     HOUR( 1) },      /* West Africa */
    { "WADST",  tDAYZONE,  HOUR( 1) },      /* West Africa Daylight*/
    { "AT",     tZONE,     HOUR( 2) },      /* Azores Daylight*/
    { "ADST",   tDAYZONE,  HOUR( 2) },      /* Azores */
    { "NFT",    tZONE,     HOUR( 7/2) },    /* Newfoundland */
    { "NDT",    tDAYZONE,  HOUR( 7/2) },    /* Newfoundland Daylight */
    { "AST",    tZONE,     HOUR( 4) },      /* Atlantic Standard */
    { "ADT",    tDAYZONE,  HOUR( 4) },      /* Atlantic Daylight */
    { "EST",    tZONE,     HOUR( 5) },      /* Eastern Standard */
    { "EDT",    tDAYZONE,  HOUR( 5) },      /* Eastern Daylight */
    { "CST",    tZONE,     HOUR( 6) },      /* Central Standard */
    { "CDT",    tDAYZONE,  HOUR( 6) },      /* Central Daylight */
    { "MST",    tZONE,     HOUR( 7) },      /* Mountain Standard */
    { "MDT",    tDAYZONE,  HOUR( 7) },      /* Mountain Daylight */
    { "PST",    tZONE,     HOUR( 8) },      /* Pacific Standard */
    { "PDT",    tDAYZONE,  HOUR( 8) },      /* Pacific Daylight */
    { "YST",    tZONE,     HOUR( 9) },      /* Yukon Standard */
    { "YDT",    tDAYZONE,  HOUR( 9) },      /* Yukon Daylight */
    { "HST",    tZONE,     HOUR(10) },      /* Hawaii Standard */
    { "HDT",    tDAYZONE,  HOUR(10) },      /* Hawaii Daylight */
    { "NT",     tZONE,     HOUR(11) },      /* Nome */
    { "NST",    tDAYZONE,  HOUR(11) },      /* Nome Daylight*/
    { "IDLW",   tZONE,     HOUR(12) },      /* International Date Line West */
    { "CET",    tZONE,    -HOUR( 1) },      /* Central European */
    { "CEST",   tDAYZONE, -HOUR( 1) },      /* Central European Summer */
    { "EET",    tZONE,    -HOUR( 2) },      /* Eastern Europe, USSR Zone 1 */
    { "EEST",   tDAYZONE, -HOUR( 2) },      /* Eastern Europe, USSR Zone 1 Daylight*/
    { "BT",     tZONE,    -HOUR( 3) },      /* Baghdad, USSR Zone 2 */
    { "BDST",   tDAYZONE, -HOUR( 3) },      /* Baghdad, USSR Zone 2 Daylight*/
    { "IT",     tZONE,    -HOUR( 7/2) },    /* Iran */
    { "IDST",   tDAYZONE, -HOUR( 7/2) },    /* Iran Daylight*/
    { "ZP4",    tZONE,    -HOUR( 4) },      /* USSR Zone 3 */
    { "ZP4S",   tDAYZONE, -HOUR( 4) },      /* USSR Zone 3 */
    { "ZP5",    tZONE,    -HOUR( 5) },      /* USSR Zone 4 */
    { "ZP5S",   tDAYZONE, -HOUR( 5) },      /* USSR Zone 4 */
    { "IST",    tZONE,    -HOUR(11/2) },    /* Indian Standard */
    { "ISDST",  tDAYZONE, -HOUR(11/2) },    /* Indian Standard */
    { "ZP6",    tZONE,    -HOUR( 6) },      /* USSR Zone 5 */
    { "ZP6S",   tDAYZONE, -HOUR( 6) },      /* USSR Zone 5 */
    { "WAST",   tZONE,    -HOUR( 7) },      /* West Australian Standard */
    { "WADT",   tDAYZONE, -HOUR( 7) },      /* West Australian Daylight */
    { "JT",     tZONE,    -HOUR(15/2) },    /* Java (3pm in Cronusland!) */
    { "JDST",   tDAYZONE, -HOUR(15/2) },    /* Java (3pm in Cronusland!) */
    { "CCT",    tZONE,    -HOUR( 8) },      /* China Coast, USSR Zone 7 */
    { "CCST",   tDAYZONE, -HOUR( 8) },      /* China Coast, USSR Zone 7 */
    { "JST",    tZONE,    -HOUR( 9) },      /* Japan Standard, USSR Zone 8 */
    { "JSDST",  tDAYZONE, -HOUR( 9) },      /* Japan Standard, USSR Zone 8 */
    { "CAST",   tZONE,    -HOUR(19/2) },    /* Central Australian Standard */
    { "CADT",   tDAYZONE, -HOUR(19/2) },    /* Central Australian Daylight */
    { "EAST",   tZONE,    -HOUR(10) },      /* Eastern Australian Standard */
    { "EADT",   tDAYZONE, -HOUR(10) },      /* Eastern Australian Daylight */
    { "NZT",    tZONE,    -HOUR(12) },      /* New Zealand */
    { "NZDT",   tDAYZONE, -HOUR(12) },      /* New Zealand Daylight */
    {  NULL  }
};

/*
 * Prototypes for procedures that are private to this file:
 */

static void SubtractUnsignedWide _ANSI_ARGS_((UnsignedWide *x,
	UnsignedWide *y, UnsignedWide *result));

/*
 *-----------------------------------------------------------------------------
 *
 * TclpGetGMTOffset --
 *
 *	This procedure gets the offset seconds that needs to be _added_ to tcl time
 *  in seconds (i.e. GMT time) to get local time needed as input to various
 *  Mac OS APIs, to convert Mac OS API output to tcl time, _subtract_ this value.
 *
 * Results:
 *	Number of seconds separating GMT time and mac.
 *
 * Side effects:
 *	None.
 *
 *-----------------------------------------------------------------------------
 */

long
TclpGetGMTOffset()
{
    if (gmt_initialized == false) {
	MachineLocation loc;
	
    Tcl_MutexLock(&gmtMutex);
	ReadLocation(&loc);
	gmt_offset = loc.u.gmtDelta & 0x00ffffff;
	if (gmt_offset & 0x00800000) {
	    gmt_offset = gmt_offset | 0xff000000;
	}
	gmt_isdst=(loc.u.dlsDelta < 0);
	gmt_initialized = true;
    Tcl_MutexUnlock(&gmtMutex);
    }
	return (gmt_offset);
}

/*
 *-----------------------------------------------------------------------------
 *
 * TclpGetSeconds --
 *
 *	This procedure returns the number of seconds from the epoch.  On
 *	the Macintosh the epoch is Midnight Jan 1, 1904.  Unfortunatly,
 *	the Macintosh doesn't tie the epoch to a particular time zone.  For
 *	Tcl we tie the epoch to GMT.  This makes the time zone date parsing
 *	code work.  The epoch for Mac-Tcl is: Midnight Jan 1, 1904 GMT.
 *
 * Results:
 *	Number of seconds from the epoch in GMT.
 *
 * Side effects:
 *	None.
 *
 *-----------------------------------------------------------------------------
 */

unsigned long
TclpGetSeconds()
{
    unsigned long seconds;

    GetDateTime(&seconds);
	return (seconds - TclpGetGMTOffset() + tcl_mac_epoch_offset);
}

/*
 *-----------------------------------------------------------------------------
 *
 * TclpGetClicks --
 *
 *	This procedure returns a value that represents the highest resolution
 *	clock available on the system.  There are no garantees on what the
 *	resolution will be.  In Tcl we will call this value a "click".  The
 *	start time is also system dependant.
 *
 * Results:
 *	Number of clicks from some start time.
 *
 * Side effects:
 *	None.
 *
 *-----------------------------------------------------------------------------
 */

unsigned long
TclpGetClicks()
{
    UnsignedWide micros;

    Microseconds(&micros);
    return micros.lo;
}

/*
 *----------------------------------------------------------------------
 *
 * TclpGetTimeZone --
 *
 *	Get the current time zone.
 *
 * Results:
 *	The return value is the local time zone, measured in
 *	minutes away from GMT (-ve for east, +ve for west).
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

int
TclpGetTimeZone (
    unsigned long  currentTime)		/* Ignored on Mac. */
{
    long offset;

    /*
     * Convert the Mac offset from seconds to minutes and
     * add an hour if we have daylight savings time.
     */
    offset = -TclpGetGMTOffset();
    offset /= 60;
    if (gmt_isdst) {
	offset += 60;
    }
    
    return offset;
}

/*
 *----------------------------------------------------------------------
 *
 * Tcl_GetTime --
 *
 *	Gets the current system time in seconds and microseconds
 *	since the beginning of the epoch: 00:00 UCT, January 1, 1970.
 *
 * Results:
 *	Returns the current time (in the local timezone) in timePtr.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

void
Tcl_GetTime(
    Tcl_Time *timePtr)		/* Location to store time information. */
{
    UnsignedWide micro;
#ifndef NO_LONG_LONG
    long long *microPtr;
#endif
	
    if (initalized == false) {
	GetDateTime(&baseSeconds);
	/*
	 * Remove the local offset that ReadDateTime() adds.
	 */
	baseSeconds -= TclpGetGMTOffset() - tcl_mac_epoch_offset;
	Microseconds(&microOffset);
	initalized = true;
    }

    Microseconds(&micro);

#ifndef NO_LONG_LONG
    microPtr = (long long *) &micro;
    *microPtr -= *((long long *) &microOffset);
    timePtr->sec = baseSeconds + (*microPtr / 1000000);
    timePtr->usec = *microPtr % 1000000;
#else
    SubtractUnsignedWide(&micro, &microOffset, &micro);

    /*
     * This lovely computation is equal to: base + (micro / 1000000)
     * For the .hi part the ratio of 0x100000000 / 1000000 has been
     * reduced to avoid overflow.  This computation certainly has 
     * problems as the .hi part gets large.  However, your application
     * would have to run for a long time to make that happen.
     */

    timePtr->sec = baseSeconds + (micro.lo / 1000000) + 
    	(long) (micro.hi * ((double) 33554432.0 / 15625.0));
    timePtr->usec = micro.lo % 1000000;
#endif
}

/*
 *----------------------------------------------------------------------
 *
 * TclpGetDate --
 *
 *	Converts raw seconds to a struct tm data structure.  The
 *	returned time will be for Greenwich Mean Time if the useGMT flag 
 *	is set.  Otherwise, the returned time will be for the local
 *	time zone.  This function is meant to be used as a replacement
 *	for localtime and gmtime which is broken on most ANSI libs
 *	on the Macintosh.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *  	The passed in struct tm data structure is modified.
 *
 *----------------------------------------------------------------------
 */

struct tm *
TclpGetDate(
    TclpTime_t time,	/* Time struct to fill. */
    int useGMT)		/* True if date should reflect GNT time. */
{
    const time_t *tp = (const time_t *)time;
    DateTimeRec dtr;
    unsigned long offset=0L;
    static struct tm statictime;
    static const short monthday[12] =
        {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334};
	    
	if(useGMT)
		SecondsToDate(*tp - tcl_mac_epoch_offset, &dtr);
	else
		SecondsToDate(*tp + TclpGetGMTOffset() - tcl_mac_epoch_offset, &dtr);
	
    statictime.tm_sec = dtr.second;
    statictime.tm_min = dtr.minute;
    statictime.tm_hour = dtr.hour;
    statictime.tm_mday = dtr.day;
    statictime.tm_mon = dtr.month - 1;
    statictime.tm_year = dtr.year - 1900;
    statictime.tm_wday = dtr.dayOfWeek - 1;
    statictime.tm_yday = monthday[statictime.tm_mon]
	+ statictime.tm_mday - 1;
    if (1 < statictime.tm_mon && !(statictime.tm_year & 3)) {
	++statictime.tm_yday;
    }
    if(useGMT)
    	statictime.tm_isdst = 0;
    else
    	statictime.tm_isdst = gmt_isdst;
    gmt_lastGetDateUseGMT=useGMT; /* hack to make TclpGetTZName below work */
    return(&statictime);
}

/*
 *----------------------------------------------------------------------
 *
 * TclpGetTZName --
 *
 *	Gets the current timezone string.
 *
 * Results:
 *	Returns a pointer to a static string, or NULL on failure.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

char *
TclpGetTZName(int dst)
{
    register TABLE *tp;
	long zonevalue=-TclpGetGMTOffset();
		
    if (gmt_isdst)
        zonevalue += HOUR(1);

	if(gmt_lastGetDateUseGMT) /* hack: if last TclpGetDate was called */
		zonevalue=0;          /* with useGMT==1 then we're using GMT  */

    for (tp = invTimezoneTable; tp->name; tp++) {
        if ((tp->value == zonevalue) && (tp->type == dst)) break;
    }
	if(!tp->name)
		tp = invTimezoneTable; /* default to unknown */

    return tp->name;
}

#ifdef NO_LONG_LONG
/*
 *----------------------------------------------------------------------
 *
 * SubtractUnsignedWide --
 *
 *	Subtracts one UnsignedWide value from another.
 *
 * Results:
 *  	The subtracted value.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static void
SubtractUnsignedWide(
    UnsignedWide *x,		/* Ptr to wide int. */
    UnsignedWide *y,		/* Ptr to wide int. */
    UnsignedWide *result)	/* Ptr to result. */
{
    result->hi = x->hi - y->hi;
    if (x->lo < y->lo) {
	result->hi--;
    }
    result->lo = x->lo - y->lo;
}
#endif
