/* * tclClock.c -- * * Contains the time and date related commands. This code * is derived from the time and date facilities of TclX, * by Mark Diekhans and Karl Lehenbauer. * * Copyright 1991-1995 Karl Lehenbauer and Mark Diekhans. * Copyright (c) 1995 Sun Microsystems, Inc. * Copyright (c) 2004 by Kevin B. Kenny. All rights reserved. * * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. * * RCS: @(#) $Id: tclClock.c,v 1.37 2004/10/30 18:04:00 kennykb Exp $ */ #include "tclInt.h" /* * Windows has mktime. The configurators do not check. */ #ifdef __WIN32__ #define HAVE_MKTIME 1 #endif /* * Thread specific data block holding a 'struct tm' for the 'gmtime' * and 'localtime' library calls. */ static Tcl_ThreadDataKey tmKey; /* * Mutex protecting 'gmtime', 'localtime' and 'mktime' calls * and the statics in the date parsing code. */ TCL_DECLARE_MUTEX(clockMutex) /* * Function prototypes for local procedures in this file: */ static struct tm* ThreadSafeLocalTime _ANSI_ARGS_(( CONST time_t* )); static void TzsetIfNecessary _ANSI_ARGS_(( void )); /* *---------------------------------------------------------------------- * * TclClockGetenvObjCmd -- * * Tcl command that reads an environment variable from the system * * Usage: * ::tcl::clock::getEnv NAME * * Parameters: * NAME - Name of the environment variable desired * * Results: * Returns a standard Tcl result. Returns an error if the * variable does not exist, with a message left in the interpreter. * Returns TCL_OK and the value of the variable if the variable * does exist, * *---------------------------------------------------------------------- */ int TclClockGetenvObjCmd( ClientData clientData, Tcl_Interp* interp, int objc, Tcl_Obj *CONST objv[] ) { CONST char* varName; CONST char* varValue; if ( objc != 2 ) { Tcl_WrongNumArgs( interp, 1, objv, "name" ); return TCL_ERROR; } varName = Tcl_GetStringFromObj( objv[1], NULL ); varValue = getenv( varName ); if ( varValue == NULL ) { Tcl_SetObjResult( interp, Tcl_NewStringObj( "variable not found", -1 ) ); return TCL_ERROR; } else { Tcl_SetObjResult( interp, Tcl_NewStringObj( varValue, -1 ) ); return TCL_OK; } } /* *------------------------------------------------------------------------- * * TclClockLocaltimeObjCmd -- * * Tcl command that extracts local time using the C library to do * it. * * Usage: * ::tcl::clock::Localtime * * Parameters: * -- A count of seconds from the Posix epoch. * * Results: * Returns a standard Tcl result. The object result is a Tcl * list containing the year, month, day, hour, minute, and second * fields of the local time. It may return an error if the * argument exceeds the arithmetic range representable by * 'time_t'. * * Side effects: * None. * * This function is used as a call of last resort if the current time * zone cannot be determined from environment variables TZ or TCL_TZ. * It attempts to use the 'localtime' library function to extract the * time and return it that way. This method suffers from Y2038 problems * on most platforms. It also provides no portable way to get the * name of the time zone. * *------------------------------------------------------------------------- */ int TclClockLocaltimeObjCmd( ClientData clientData, /* Unused */ Tcl_Interp* interp, /* Tcl interpreter */ int objc, /* Parameter count */ Tcl_Obj* CONST* objv ) /* Parameter vector */ { Tcl_WideInt tick; /* Time to convert */ time_t tock; struct tm* timeVal; /* Time after conversion */ Tcl_Obj* returnVec[ 6 ]; /* Check args */ if ( objc != 2 ) { Tcl_WrongNumArgs( interp, 1, objv, "seconds" ); return TCL_ERROR; } if ( Tcl_GetWideIntFromObj( interp, objv[1], &tick ) != TCL_OK ) { return TCL_ERROR; } /* Convert the time, checking for overflow */ tock = (time_t) tick; if ( (Tcl_WideInt) tock != tick ) { Tcl_SetObjResult ( interp, Tcl_NewStringObj("number too large to represent as a Posix time", -1) ); Tcl_SetErrorCode( interp, "CLOCK", "argTooLarge", (char*) NULL ); return TCL_ERROR; } TzsetIfNecessary(); timeVal = ThreadSafeLocalTime( &tock ); /* Package the results */ returnVec[0] = Tcl_NewIntObj( timeVal->tm_year + 1900 ); returnVec[1] = Tcl_NewIntObj( timeVal->tm_mon + 1); returnVec[2] = Tcl_NewIntObj( timeVal->tm_mday ); returnVec[3] = Tcl_NewIntObj( timeVal->tm_hour ); returnVec[4] = Tcl_NewIntObj( timeVal->tm_min ); returnVec[5] = Tcl_NewIntObj( timeVal->tm_sec ); Tcl_SetObjResult( interp, Tcl_NewListObj( 6, returnVec ) ); return TCL_OK; } /* *---------------------------------------------------------------------- * * ThreadSafeLocalTime -- * * Wrapper around the 'localtime' library function to make it thread * safe. * * Results: * Returns a pointer to a 'struct tm' in thread-specific data. * * Side effects: * Invokes localtime or localtime_r as appropriate. * *---------------------------------------------------------------------- */ static struct tm * ThreadSafeLocalTime(timePtr) CONST time_t *timePtr; /* Pointer to the number of seconds * since the local system's epoch */ { /* * Get a thread-local buffer to hold the returned time. */ struct tm *tmPtr = (struct tm *) Tcl_GetThreadData(&tmKey, (int) sizeof(struct tm)); #ifdef HAVE_LOCALTIME_R localtime_r(timePtr, tmPtr); #else Tcl_MutexLock(&clockMutex); memcpy((VOID *) tmPtr, (VOID *) localtime(timePtr), sizeof(struct tm)); Tcl_MutexUnlock(&clockMutex); #endif return tmPtr; } /* *---------------------------------------------------------------------- * * TclClockMktimeObjCmd -- * * Determine seconds from the epoch, given the fields of a local * time. * * Usage: * mktime * * Parameters: * year -- Calendar year * month -- Calendar month * day -- Calendar day * hour -- Hour of day (00-23) * minute -- Minute of hour * second -- Second of minute * * Results: * Returns the given local time. * * Errors: * Returns an error if the 'mktime' function does not exist in the * C library, or if the given time cannot be converted. * * Side effects: * None. * *---------------------------------------------------------------------- */ int TclClockMktimeObjCmd( ClientData clientData, /* Unused */ Tcl_Interp* interp, /* Tcl interpreter */ int objc, /* Parameter count */ Tcl_Obj* CONST* objv ) /* Parameter vector */ { #ifndef HAVE_MKTIME Tcl_SetObjResult( interp, Tcl_NewStringObj( "cannot determine local time", -1 ) ); return TCL_ERROR; #else int i; struct tm toConvert; /* Time to be converted */ time_t convertedTime; /* Time converted from mktime */ int localErrno; /* Convert parameters */ if ( objc != 7 ) { Tcl_WrongNumArgs( interp, 1, objv, "year month day hour minute second" ); return TCL_ERROR; } if ( Tcl_GetIntFromObj( interp, objv[1], &i ) != TCL_OK ) { return TCL_ERROR; } toConvert.tm_year = i - 1900; if ( Tcl_GetIntFromObj( interp, objv[2], &i ) != TCL_OK ) { return TCL_ERROR; } toConvert.tm_mon = i - 1; if ( Tcl_GetIntFromObj( interp, objv[3], &i ) != TCL_OK ) { return TCL_ERROR; } toConvert.tm_mday = i; if ( Tcl_GetIntFromObj( interp, objv[4], &i ) != TCL_OK ) { return TCL_ERROR; } toConvert.tm_hour = i; if ( Tcl_GetIntFromObj( interp, objv[5], &i ) != TCL_OK ) { return TCL_ERROR; } toConvert.tm_min = i; if ( Tcl_GetIntFromObj( interp, objv[6], &i ) != TCL_OK ) { return TCL_ERROR; } toConvert.tm_sec = i; toConvert.tm_isdst = -1; toConvert.tm_wday = 0; toConvert.tm_yday = 0; /* Convert the time. It is rumored that mktime is not thread * safe on some platforms. */ TzsetIfNecessary(); Tcl_MutexLock( &clockMutex ); errno = 0; convertedTime = mktime( &toConvert ); localErrno = errno; Tcl_MutexUnlock( &clockMutex ); /* Return the converted time, or an error if conversion fails */ if ( localErrno != 0 ) { Tcl_SetObjResult ( interp, Tcl_NewStringObj( "time value too large/small to represent", -1 ) ); return TCL_ERROR; } else { Tcl_SetObjResult( interp, Tcl_NewWideIntObj( (Tcl_WideInt) convertedTime ) ); return TCL_OK; } #endif } /*---------------------------------------------------------------------- * * TclClockClicksObjCmd -- * * Returns a high-resolution counter. * * Results: * Returns a standard Tcl result. * * Side effects: * None. * * This function implements the 'clock clicks' Tcl command. Refer * to the user documentation for details on what it does. * *---------------------------------------------------------------------- */ int TclClockClicksObjCmd(clientData, interp, objc, objv) ClientData clientData; /* Client data is unused */ Tcl_Interp* interp; /* Tcl interpreter */ int objc; /* Parameter count */ Tcl_Obj* CONST* objv; /* Parameter values */ { static CONST char *clicksSwitches[] = { "-milliseconds", "-microseconds", (char*) NULL }; enum ClicksSwitch { CLICKS_MILLIS, CLICKS_MICROS, CLICKS_NATIVE }; int index = CLICKS_NATIVE; Tcl_Time now; switch (objc) { case 1: break; case 2: if (Tcl_GetIndexFromObj(interp, objv[1], clicksSwitches, "option", 0, &index) != TCL_OK) { return TCL_ERROR; } break; default: Tcl_WrongNumArgs(interp, 1, objv, "?option?"); return TCL_ERROR; } switch (index) { case CLICKS_MILLIS: Tcl_GetTime(&now); Tcl_SetObjResult(interp, Tcl_NewWideIntObj( (Tcl_WideInt) now.sec * 1000 + now.usec / 1000 ) ); break; case CLICKS_NATIVE: #if 0 /* * The following code will be used once this is incorporated * into Tcl. But TEA bugs prevent it for right now. :( * So we fall through this case and return the microseconds * instead. */ Tcl_SetObjResult(interp, Tcl_NewWideIntObj( (Tcl_WideInt) TclpGetClicks())); break; #endif case CLICKS_MICROS: Tcl_GetTime(&now); Tcl_SetObjResult(interp, Tcl_NewWideIntObj( ((Tcl_WideInt) now.sec * 1000000) + now.usec)); break; } return TCL_OK; } /*---------------------------------------------------------------------- * * TclClockMillisecondsObjCmd - * * Returns a count of milliseconds since the epoch. * * Results: * Returns a standard Tcl result. * * Side effects: * None. * * This function implements the 'clock milliseconds' Tcl command. Refer * to the user documentation for details on what it does. * *---------------------------------------------------------------------- */ int TclClockMillisecondsObjCmd(clientData, interp, objc, objv) ClientData clientData; /* Client data is unused */ Tcl_Interp* interp; /* Tcl interpreter */ int objc; /* Parameter count */ Tcl_Obj* CONST* objv; /* Parameter values */ { Tcl_Time now; if (objc != 1) { Tcl_WrongNumArgs(interp, 1, objv, NULL); return TCL_ERROR; } Tcl_GetTime(&now); Tcl_SetObjResult(interp, Tcl_NewWideIntObj( (Tcl_WideInt) now.sec * 1000 + now.usec / 1000)); return TCL_OK; } /*---------------------------------------------------------------------- * * TclClockMicrosecondsObjCmd - * * Returns a count of microseconds since the epoch. * * Results: * Returns a standard Tcl result. * * Side effects: * None. * * This function implements the 'clock microseconds' Tcl command. Refer * to the user documentation for details on what it does. * *---------------------------------------------------------------------- */ int TclClockMicrosecondsObjCmd(clientData, interp, objc, objv) ClientData clientData; /* Client data is unused */ Tcl_Interp* interp; /* Tcl interpreter */ int objc; /* Parameter count */ Tcl_Obj* CONST* objv; /* Parameter values */ { Tcl_Time now; if (objc != 1) { Tcl_WrongNumArgs(interp, 1, objv, NULL); return TCL_ERROR; } Tcl_GetTime(&now); Tcl_SetObjResult(interp, Tcl_NewWideIntObj( ((Tcl_WideInt) now.sec * 1000000) + now.usec)); return TCL_OK; } /*---------------------------------------------------------------------- * * TclClockSecondsObjCmd - * * Returns a count of microseconds since the epoch. * * Results: * Returns a standard Tcl result. * * Side effects: * None. * * This function implements the 'clock seconds' Tcl command. Refer * to the user documentation for details on what it does. * *---------------------------------------------------------------------- */ int TclClockSecondsObjCmd(clientData, interp, objc, objv) ClientData clientData; /* Client data is unused */ Tcl_Interp* interp; /* Tcl interpreter */ int objc; /* Parameter count */ Tcl_Obj* CONST* objv; /* Parameter values */ { Tcl_Time now; if (objc != 1) { Tcl_WrongNumArgs(interp, 1, objv, NULL); return TCL_ERROR; } Tcl_GetTime(&now); Tcl_SetObjResult(interp, Tcl_NewWideIntObj((Tcl_WideInt) now.sec)); return TCL_OK; } /* *---------------------------------------------------------------------- * * TzsetIfNecessary -- * * Calls the tzset() library function if the contents of the TZ * environment variable has changed. * * Results: * None. * * Side effects: * Calls tzset. * *---------------------------------------------------------------------- */ static void TzsetIfNecessary() { static char* tzWas = NULL; /* Previous value of TZ, protected by * clockMutex. */ CONST char* tzIsNow; /* Current value of TZ */ Tcl_MutexLock( &clockMutex ); tzIsNow = getenv( "TZ" ); if ( tzIsNow != NULL && ( tzWas == NULL || strcmp( tzIsNow, tzWas ) != 0 ) ) { tzset(); if ( tzWas != NULL ) { ckfree( tzWas ); } tzWas = ckalloc( strlen( tzIsNow ) + 1 ); strcpy( tzWas, tzIsNow ); } else if ( tzIsNow == NULL && tzWas != NULL ) { tzset(); ckfree( tzWas ); tzWas = NULL; } Tcl_MutexUnlock( &clockMutex ); }