diff options
Diffstat (limited to 'generic/tclClock.c')
| -rw-r--r-- | generic/tclClock.c | 2211 | 
1 files changed, 1940 insertions, 271 deletions
| diff --git a/generic/tclClock.c b/generic/tclClock.c index 5cfc675..15f29e5 100644 --- a/generic/tclClock.c +++ b/generic/tclClock.c @@ -1,25 +1,136 @@ -/*  +/*   * 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. + *	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.14 2002/01/25 20:40:55 dgp Exp $ + * See the file "license.terms" for information on usage and redistribution of + * this file, and for a DISCLAIMER OF ALL WARRANTIES.   */ -#include "tcl.h"  #include "tclInt.h" -#include "tclPort.h"  /* - * The date parsing stuff uses lexx and has tons o statics. + * Windows has mktime. The configurators do not check. + */ + +#ifdef _WIN32 +#define HAVE_MKTIME 1 +#endif + +/* + * Constants + */ + +#define JULIAN_DAY_POSIX_EPOCH		2440588 +#define SECONDS_PER_DAY			86400 +#define JULIAN_SEC_POSIX_EPOCH	      (((Tcl_WideInt) JULIAN_DAY_POSIX_EPOCH) \ +					* SECONDS_PER_DAY) +#define FOUR_CENTURIES			146097	/* days */ +#define JDAY_1_JAN_1_CE_JULIAN		1721424 +#define JDAY_1_JAN_1_CE_GREGORIAN	1721426 +#define ONE_CENTURY_GREGORIAN		36524	/* days */ +#define FOUR_YEARS			1461	/* days */ +#define ONE_YEAR			365	/* days */ + +/* + * Table of the days in each month, leap and common years + */ + +static const int hath[2][12] = { +    {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 daysInPriorMonths[2][13] = { +    {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365}, +    {0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366} +}; + +/* + * Enumeration of the string literals used in [clock] + */ + +typedef enum ClockLiteral { +    LIT__NIL, +    LIT__DEFAULT_FORMAT, +    LIT_BCE,		LIT_C, +    LIT_CANNOT_USE_GMT_AND_TIMEZONE, +    LIT_CE, +    LIT_DAYOFMONTH,	LIT_DAYOFWEEK,		LIT_DAYOFYEAR, +    LIT_ERA,		LIT_GMT,		LIT_GREGORIAN, +    LIT_INTEGER_VALUE_TOO_LARGE, +    LIT_ISO8601WEEK,	LIT_ISO8601YEAR, +    LIT_JULIANDAY,	LIT_LOCALSECONDS, +    LIT_MONTH, +    LIT_SECONDS,	LIT_TZNAME,		LIT_TZOFFSET, +    LIT_YEAR, +    LIT__END +} ClockLiteral; +static const char *const literals[] = { +    "", +    "%a %b %d %H:%M:%S %Z %Y", +    "BCE",		"C", +    "cannot use -gmt and -timezone in same call", +    "CE", +    "dayOfMonth",	"dayOfWeek",		"dayOfYear", +    "era",		":GMT",			"gregorian", +    "integer value too large to represent", +    "iso8601Week",	"iso8601Year", +    "julianDay",	"localSeconds", +    "month", +    "seconds",		"tzName",		"tzOffset", +    "year" +}; + +/* + * Structure containing the client data for [clock] + */ + +typedef struct ClockClientData { +    int refCount;		/* Number of live references. */ +    Tcl_Obj **literals;		/* Pool of object literals. */ +} ClockClientData; + +/* + * Structure containing the fields used in [clock format] and [clock scan] + */ + +typedef struct TclDateFields { +    Tcl_WideInt seconds;	/* Time expressed in seconds from the Posix +				 * epoch */ +    Tcl_WideInt localSeconds;	/* Local time expressed in nominal seconds +				 * from the Posix epoch */ +    int tzOffset;		/* Time zone offset in seconds east of +				 * Greenwich */ +    Tcl_Obj *tzName;		/* Time zone name */ +    int julianDay;		/* Julian Day Number in local time zone */ +    enum {BCE=1, CE=0} era;	/* Era */ +    int gregorian;		/* Flag == 1 if the date is Gregorian */ +    int year;			/* Year of the era */ +    int dayOfYear;		/* Day of the year (1 January == 1) */ +    int month;			/* Month number */ +    int dayOfMonth;		/* Day of the month */ +    int iso8601Year;		/* ISO8601 week-based year */ +    int iso8601Week;		/* ISO8601 week number */ +    int dayOfWeek;		/* Day of the week */ +} TclDateFields; +static const char *const eras[] = { "CE", "BCE", NULL }; + +/* + * 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) @@ -28,348 +139,1906 @@ TCL_DECLARE_MUTEX(clockMutex)   * Function prototypes for local procedures in this file:   */ -static int		FormatClock _ANSI_ARGS_((Tcl_Interp *interp, -			    unsigned long clockVal, int useGMT, -			    char *format)); +static int		ConvertUTCToLocal(Tcl_Interp *, +			    TclDateFields *, Tcl_Obj *, int); +static int		ConvertUTCToLocalUsingTable(Tcl_Interp *, +			    TclDateFields *, int, Tcl_Obj *const[]); +static int		ConvertUTCToLocalUsingC(Tcl_Interp *, +			    TclDateFields *, int); +static int		ConvertLocalToUTC(Tcl_Interp *, +			    TclDateFields *, Tcl_Obj *, int); +static int		ConvertLocalToUTCUsingTable(Tcl_Interp *, +			    TclDateFields *, int, Tcl_Obj *const[]); +static int		ConvertLocalToUTCUsingC(Tcl_Interp *, +			    TclDateFields *, int); +static Tcl_Obj *	LookupLastTransition(Tcl_Interp *, Tcl_WideInt, +			    int, Tcl_Obj *const *); +static void		GetYearWeekDay(TclDateFields *, int); +static void		GetGregorianEraYearDay(TclDateFields *, int); +static void		GetMonthDay(TclDateFields *); +static void		GetJulianDayFromEraYearWeekDay(TclDateFields *, int); +static void		GetJulianDayFromEraYearMonthDay(TclDateFields *, int); +static int		IsGregorianLeapYear(TclDateFields *); +static int		WeekdayOnOrBefore(int, int); +static int		ClockClicksObjCmd( +			    ClientData clientData, Tcl_Interp *interp, +			    int objc, Tcl_Obj *const objv[]); +static int		ClockConvertlocaltoutcObjCmd( +			    ClientData clientData, Tcl_Interp *interp, +			    int objc, Tcl_Obj *const objv[]); +static int		ClockGetdatefieldsObjCmd( +			    ClientData clientData, Tcl_Interp *interp, +			    int objc, Tcl_Obj *const objv[]); +static int		ClockGetjuliandayfromerayearmonthdayObjCmd( +			    ClientData clientData, Tcl_Interp *interp, +			    int objc, Tcl_Obj *const objv[]); +static int		ClockGetjuliandayfromerayearweekdayObjCmd( +			    ClientData clientData, Tcl_Interp *interp, +			    int objc, Tcl_Obj *const objv[]); +static int		ClockGetenvObjCmd( +			    ClientData clientData, Tcl_Interp *interp, +			    int objc, Tcl_Obj *const objv[]); +static int		ClockMicrosecondsObjCmd( +			    ClientData clientData, Tcl_Interp *interp, +			    int objc, Tcl_Obj *const objv[]); +static int		ClockMillisecondsObjCmd( +			    ClientData clientData, Tcl_Interp *interp, +			    int objc, Tcl_Obj *const objv[]); +static int		ClockParseformatargsObjCmd( +			    ClientData clientData, Tcl_Interp *interp, +			    int objc, Tcl_Obj *const objv[]); +static int		ClockSecondsObjCmd( +			    ClientData clientData, Tcl_Interp *interp, +			    int objc, Tcl_Obj *const objv[]); +static struct tm *	ThreadSafeLocalTime(const time_t *); +static void		TzsetIfNecessary(void); +static void		ClockDeleteCmdProc(ClientData); + +/* + * Structure containing description of "native" clock commands to create. + */ + +struct ClockCommand { +    const char *name;		/* The tail of the command name. The full name +				 * is "::tcl::clock::<name>". When NULL marks +				 * the end of the table. */ +    Tcl_ObjCmdProc *objCmdProc;	/* Function that implements the command. This +				 * will always have the ClockClientData sent +				 * to it, but may well ignore this data. */ +}; + +static const struct ClockCommand clockCommands[] = { +    { "clicks",			ClockClicksObjCmd }, +    { "getenv",			ClockGetenvObjCmd }, +    { "microseconds",		ClockMicrosecondsObjCmd }, +    { "milliseconds",		ClockMillisecondsObjCmd }, +    { "seconds",		ClockSecondsObjCmd }, +    { "Oldscan",		TclClockOldscanObjCmd }, +    { "ConvertLocalToUTC",	ClockConvertlocaltoutcObjCmd }, +    { "GetDateFields",		ClockGetdatefieldsObjCmd }, +    { "GetJulianDayFromEraYearMonthDay", +		ClockGetjuliandayfromerayearmonthdayObjCmd }, +    { "GetJulianDayFromEraYearWeekDay", +		ClockGetjuliandayfromerayearweekdayObjCmd }, +    { "ParseFormatArgs",	ClockParseformatargsObjCmd }, +    { NULL, NULL } +}; + +/* + *---------------------------------------------------------------------- + * + * TclClockInit -- + * + *	Registers the 'clock' subcommands with the Tcl interpreter and + *	initializes its client data (which consists mostly of constant + *	Tcl_Obj's that it is too much trouble to keep recreating). + * + * Results: + *	None. + * + * Side effects: + *	Installs the commands and creates the client data + * + *---------------------------------------------------------------------- + */ + +void +TclClockInit( +    Tcl_Interp *interp)		/* Tcl interpreter */ +{ +    const struct ClockCommand *clockCmdPtr; +    char cmdName[50];		/* Buffer large enough to hold the string +				 *::tcl::clock::GetJulianDayFromEraYearMonthDay +				 * plus a terminating NUL. */ +    ClockClientData *data; +    int i; + +    /* +     * Safe interps get [::clock] as alias to a master, so do not need their +     * own copies of the support routines. +     */ + +    if (Tcl_IsSafe(interp)) { +	return; +    } + +    /* +     * Create the client data, which is a refcounted literal pool. +     */ + +    data = ckalloc(sizeof(ClockClientData)); +    data->refCount = 0; +    data->literals = ckalloc(LIT__END * sizeof(Tcl_Obj*)); +    for (i = 0; i < LIT__END; ++i) { +	data->literals[i] = Tcl_NewStringObj(literals[i], -1); +	Tcl_IncrRefCount(data->literals[i]); +    } + +    /* +     * Install the commands. +     */ + +#define TCL_CLOCK_PREFIX_LEN 14 /* == strlen("::tcl::clock::") */ +    memcpy(cmdName, "::tcl::clock::", TCL_CLOCK_PREFIX_LEN); +    for (clockCmdPtr=clockCommands ; clockCmdPtr->name!=NULL ; clockCmdPtr++) { +	strcpy(cmdName + TCL_CLOCK_PREFIX_LEN, clockCmdPtr->name); +	data->refCount++; +	Tcl_CreateObjCommand(interp, cmdName, clockCmdPtr->objCmdProc, data, +		ClockDeleteCmdProc); +    } +}  /* - *------------------------------------------------------------------------- + *----------------------------------------------------------------------   * - * Tcl_ClockObjCmd -- + * ClockConvertlocaltoutcObjCmd --   * - *	This procedure is invoked to process the "clock" Tcl command. - *	See the user documentation for details on what it does. + *	Tcl command that converts a UTC time to a local time by whatever means + *	is available. + * + * Usage: + *	::tcl::clock::ConvertUTCToLocal dictionary tzdata changeover + * + * Parameters: + *	dict - Dictionary containing a 'localSeconds' entry. + *	tzdata - Time zone data + *	changeover - Julian Day of the adoption of the Gregorian calendar.   *   * Results: - *	A standard Tcl result. + *	Returns a standard Tcl result.   *   * Side effects: - *	See the user documentation. + *	On success, sets the interpreter result to the given dictionary + *	augmented with a 'seconds' field giving the UTC time. On failure, + *	leaves an error message in the interpreter result. + * + *---------------------------------------------------------------------- + */ + +static int +ClockConvertlocaltoutcObjCmd( +    ClientData clientData,	/* Client data */ +    Tcl_Interp *interp,		/* Tcl interpreter */ +    int objc,			/* Parameter count */ +    Tcl_Obj *const *objv)	/* Parameter vector */ +{ +    ClockClientData *data = clientData; +    Tcl_Obj *const *literals = data->literals; +    Tcl_Obj *secondsObj; +    Tcl_Obj *dict; +    int changeover; +    TclDateFields fields; +    int created = 0; +    int status; + +    /* +     * Check params and convert time. +     */ + +    if (objc != 4) { +	Tcl_WrongNumArgs(interp, 1, objv, "dict tzdata changeover"); +	return TCL_ERROR; +    } +    dict = objv[1]; +    if (Tcl_DictObjGet(interp, dict, literals[LIT_LOCALSECONDS], +	    &secondsObj)!= TCL_OK) { +	return TCL_ERROR; +    } +    if (secondsObj == NULL) { +	Tcl_SetObjResult(interp, Tcl_NewStringObj("key \"localseconds\" not " +		"found in dictionary", -1)); +	return TCL_ERROR; +    } +    if ((Tcl_GetWideIntFromObj(interp, secondsObj, +	    &fields.localSeconds) != TCL_OK) +	|| (TclGetIntFromObj(interp, objv[3], &changeover) != TCL_OK) +	|| ConvertLocalToUTC(interp, &fields, objv[2], changeover)) { +	return TCL_ERROR; +    } + +    /* +     * Copy-on-write; set the 'seconds' field in the dictionary and place the +     * modified dictionary in the interpreter result. +     */ + +    if (Tcl_IsShared(dict)) { +	dict = Tcl_DuplicateObj(dict); +	created = 1; +	Tcl_IncrRefCount(dict); +    } +    status = Tcl_DictObjPut(interp, dict, literals[LIT_SECONDS], +	    Tcl_NewWideIntObj(fields.seconds)); +    if (status == TCL_OK) { +	Tcl_SetObjResult(interp, dict); +    } +    if (created) { +	Tcl_DecrRefCount(dict); +    } +    return status; +} + +/* + *---------------------------------------------------------------------- + * + * ClockGetdatefieldsObjCmd -- + * + *	Tcl command that determines the values that [clock format] will use in + *	formatting a date, and populates a dictionary with them. + * + * Usage: + *	::tcl::clock::GetDateFields seconds tzdata changeover + * + * Parameters: + *	seconds - Time expressed in seconds from the Posix epoch. + *	tzdata - Time zone data of the time zone in which time is to be + *		 expressed. + *	changeover - Julian Day Number at which the current locale adopted + *		     the Gregorian calendar + * + * Results: + *	Returns a dictonary populated with the fields: + *		seconds - Seconds from the Posix epoch + *		localSeconds - Nominal seconds from the Posix epoch in the + *			       local time zone. + *		tzOffset - Time zone offset in seconds east of Greenwich + *		tzName - Time zone name + *		julianDay - Julian Day Number in the local time zone   * - *------------------------------------------------------------------------- + *----------------------------------------------------------------------   */  int -Tcl_ClockObjCmd (client, interp, objc, objv) -    ClientData client;			/* Not used. */ -    Tcl_Interp *interp;			/* Current interpreter. */ -    int objc;				/* Number of arguments. */ -    Tcl_Obj *CONST objv[];		/* Argument values. */ +ClockGetdatefieldsObjCmd( +    ClientData clientData,	/* Opaque pointer to literal pool, etc. */ +    Tcl_Interp *interp,		/* Tcl interpreter */ +    int objc,			/* Parameter count */ +    Tcl_Obj *const *objv)	/* Parameter vector */  { -    Tcl_Obj *resultPtr; -    int index; -    Tcl_Obj *CONST *objPtr; -    int useGMT = 0; -    char *format = "%a %b %d %X %Z %Y"; -    int dummy; -    unsigned long baseClock, clockVal; -    long zone; -    Tcl_Obj *baseObjPtr = NULL; -    char *scanStr; -     -    static CONST char *switches[] = -	{"clicks", "format", "scan", "seconds", (char *) NULL}; -    enum command { COMMAND_CLICKS, COMMAND_FORMAT, COMMAND_SCAN, -		       COMMAND_SECONDS -    }; -    static CONST char *formatSwitches[] = {"-format", "-gmt", (char *) NULL}; -    static CONST char *scanSwitches[] = {"-base", "-gmt", (char *) NULL}; +    TclDateFields fields; +    Tcl_Obj *dict; +    ClockClientData *data = clientData; +    Tcl_Obj *const *literals = data->literals; +    int changeover; + +    /* +     * Check params. +     */ -    resultPtr = Tcl_GetObjResult(interp); -    if (objc < 2) { -	Tcl_WrongNumArgs(interp, 1, objv, "option ?arg ...?"); +    if (objc != 4) { +	Tcl_WrongNumArgs(interp, 1, objv, "seconds tzdata changeover");  	return TCL_ERROR;      } +    if (Tcl_GetWideIntFromObj(interp, objv[1], &fields.seconds) != TCL_OK +	    || TclGetIntFromObj(interp, objv[3], &changeover) != TCL_OK) { +	return TCL_ERROR; +    } + +    /* +     * fields.seconds could be an unsigned number that overflowed. Make sure +     * that it isn't. +     */ -    if (Tcl_GetIndexFromObj(interp, objv[1], switches, "option", 0, &index) -	    != TCL_OK) { +    if (objv[1]->typePtr == &tclBignumType) { +	Tcl_SetObjResult(interp, literals[LIT_INTEGER_VALUE_TOO_LARGE]);  	return TCL_ERROR;      } -    switch ((enum command) index) { -	case COMMAND_CLICKS:	{		/* clicks */ -	    int forceMilli = 0; - -	    if (objc == 3) { -		format = Tcl_GetStringFromObj(objv[2], &index); -		if (strncmp(format, "-milliseconds", -			(unsigned int) index) == 0) { -		    forceMilli = 1; -		} else { -		    Tcl_AppendStringsToObj(resultPtr, -			    "bad switch \"", format, -			    "\": must be -milliseconds", (char *) NULL); -		    return TCL_ERROR; -		} -	    } else if (objc != 2) { -		Tcl_WrongNumArgs(interp, 2, objv, "?-milliseconds?"); -		return TCL_ERROR; + +    /* +     * Convert UTC time to local. +     */ + +    if (ConvertUTCToLocal(interp, &fields, objv[2], changeover) != TCL_OK) { +	return TCL_ERROR; +    } + +    /* +     * Extract Julian day. +     */ + +    fields.julianDay = (int) ((fields.localSeconds + JULIAN_SEC_POSIX_EPOCH) +	    / SECONDS_PER_DAY); + +    /* +     * Convert to Julian or Gregorian calendar. +     */ + +    GetGregorianEraYearDay(&fields, changeover); +    GetMonthDay(&fields); +    GetYearWeekDay(&fields, changeover); + +    dict = Tcl_NewDictObj(); +    Tcl_DictObjPut(NULL, dict, literals[LIT_LOCALSECONDS], +	    Tcl_NewWideIntObj(fields.localSeconds)); +    Tcl_DictObjPut(NULL, dict, literals[LIT_SECONDS], +	    Tcl_NewWideIntObj(fields.seconds)); +    Tcl_DictObjPut(NULL, dict, literals[LIT_TZNAME], fields.tzName); +    Tcl_DecrRefCount(fields.tzName); +    Tcl_DictObjPut(NULL, dict, literals[LIT_TZOFFSET], +	    Tcl_NewIntObj(fields.tzOffset)); +    Tcl_DictObjPut(NULL, dict, literals[LIT_JULIANDAY], +	    Tcl_NewIntObj(fields.julianDay)); +    Tcl_DictObjPut(NULL, dict, literals[LIT_GREGORIAN], +	    Tcl_NewIntObj(fields.gregorian)); +    Tcl_DictObjPut(NULL, dict, literals[LIT_ERA], +	    literals[fields.era ? LIT_BCE : LIT_CE]); +    Tcl_DictObjPut(NULL, dict, literals[LIT_YEAR], +	    Tcl_NewIntObj(fields.year)); +    Tcl_DictObjPut(NULL, dict, literals[LIT_DAYOFYEAR], +	    Tcl_NewIntObj(fields.dayOfYear)); +    Tcl_DictObjPut(NULL, dict, literals[LIT_MONTH], +	    Tcl_NewIntObj(fields.month)); +    Tcl_DictObjPut(NULL, dict, literals[LIT_DAYOFMONTH], +	    Tcl_NewIntObj(fields.dayOfMonth)); +    Tcl_DictObjPut(NULL, dict, literals[LIT_ISO8601YEAR], +	    Tcl_NewIntObj(fields.iso8601Year)); +    Tcl_DictObjPut(NULL, dict, literals[LIT_ISO8601WEEK], +	    Tcl_NewIntObj(fields.iso8601Week)); +    Tcl_DictObjPut(NULL, dict, literals[LIT_DAYOFWEEK], +	    Tcl_NewIntObj(fields.dayOfWeek)); +    Tcl_SetObjResult(interp, dict); + +    return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * ClockGetjuliandayfromerayearmonthdayObjCmd -- + * + *	Tcl command that converts a time from era-year-month-day to a Julian + *	Day Number. + * + * Parameters: + *	dict - Dictionary that contains 'era', 'year', 'month' and + *	       'dayOfMonth' keys. + *	changeover - Julian Day of changeover to the Gregorian calendar + * + * Results: + *	Result is either TCL_OK, with the interpreter result being the + *	dictionary augmented with a 'julianDay' key, or TCL_ERROR, + *	with the result being an error message. + * + *---------------------------------------------------------------------- + */ + +static int +ClockGetjuliandayfromerayearmonthdayObjCmd( +    ClientData clientData,	/* Opaque pointer to literal pool, etc. */ +    Tcl_Interp *interp,		/* Tcl interpreter */ +    int objc,			/* Parameter count */ +    Tcl_Obj *const *objv)	/* Parameter vector */ +{ +    TclDateFields fields; +    Tcl_Obj *dict; +    ClockClientData *data = clientData; +    Tcl_Obj *const *literals = data->literals; +    Tcl_Obj *fieldPtr; +    int changeover; +    int copied = 0; +    int status; +    int era = 0; + +    /* +     * Check params. +     */ + +    if (objc != 3) { +	Tcl_WrongNumArgs(interp, 1, objv, "dict changeover"); +	return TCL_ERROR; +    } +    dict = objv[1]; +    if (Tcl_DictObjGet(interp, dict, literals[LIT_ERA], &fieldPtr) != TCL_OK +		 || fieldPtr == NULL +	    || Tcl_GetIndexFromObj(interp, fieldPtr, eras, "era", TCL_EXACT, +		&era) != TCL_OK +	    || Tcl_DictObjGet(interp, dict, literals[LIT_YEAR], &fieldPtr) != TCL_OK +		 || fieldPtr == NULL +	    || TclGetIntFromObj(interp, fieldPtr, &fields.year) != TCL_OK +	    || Tcl_DictObjGet(interp, dict, literals[LIT_MONTH],	&fieldPtr) != TCL_OK +		 || fieldPtr == NULL +	    || TclGetIntFromObj(interp, fieldPtr, &fields.month) != TCL_OK +	    || Tcl_DictObjGet(interp, dict, literals[LIT_DAYOFMONTH], &fieldPtr) != TCL_OK +		 || fieldPtr == NULL +	    || TclGetIntFromObj(interp, fieldPtr, &fields.dayOfMonth)!=TCL_OK +	    || TclGetIntFromObj(interp, objv[2], &changeover) != TCL_OK) { +    if (fieldPtr == NULL) +	Tcl_SetObjResult(interp, Tcl_NewStringObj("expected key(s) not found in dictionary", -1)); +	return TCL_ERROR; +    } +    fields.era = era; + +    /* +     * Get Julian day. +     */ + +    GetJulianDayFromEraYearMonthDay(&fields, changeover); + +    /* +     * Store Julian day in the dictionary - copy on write. +     */ + +    if (Tcl_IsShared(dict)) { +	dict = Tcl_DuplicateObj(dict); +	Tcl_IncrRefCount(dict); +	copied = 1; +    } +    status = Tcl_DictObjPut(interp, dict, literals[LIT_JULIANDAY], +	    Tcl_NewIntObj(fields.julianDay)); +    if (status == TCL_OK) { +	Tcl_SetObjResult(interp, dict); +    } +    if (copied) { +	Tcl_DecrRefCount(dict); +    } +    return status; +} + +/* + *---------------------------------------------------------------------- + * + * ClockGetjuliandayfromerayearweekdayObjCmd -- + * + *	Tcl command that converts a time from the ISO calendar to a Julian Day + *	Number. + * + * Parameters: + *	dict - Dictionary that contains 'era', 'iso8601Year', 'iso8601Week' + *	       and 'dayOfWeek' keys. + *	changeover - Julian Day of changeover to the Gregorian calendar + * + * Results: + *	Result is either TCL_OK, with the interpreter result being the + *	dictionary augmented with a 'julianDay' key, or TCL_ERROR, with the + *	result being an error message. + * + *---------------------------------------------------------------------- + */ + +static int +ClockGetjuliandayfromerayearweekdayObjCmd( +    ClientData clientData,	/* Opaque pointer to literal pool, etc. */ +    Tcl_Interp *interp,		/* Tcl interpreter */ +    int objc,			/* Parameter count */ +    Tcl_Obj *const *objv)	/* Parameter vector */ +{ +    TclDateFields fields; +    Tcl_Obj *dict; +    ClockClientData *data = clientData; +    Tcl_Obj *const *literals = data->literals; +    Tcl_Obj *fieldPtr; +    int changeover; +    int copied = 0; +    int status; +    int era = 0; + +    /* +     * Check params. +     */ + +    if (objc != 3) { +	Tcl_WrongNumArgs(interp, 1, objv, "dict changeover"); +	return TCL_ERROR; +    } +    dict = objv[1]; +    if (Tcl_DictObjGet(interp, dict, literals[LIT_ERA], &fieldPtr) != TCL_OK +		 || fieldPtr == NULL +	    || Tcl_GetIndexFromObj(interp, fieldPtr, eras, "era", TCL_EXACT, +		&era) != TCL_OK +	    || Tcl_DictObjGet(interp, dict, literals[LIT_ISO8601YEAR], &fieldPtr) != TCL_OK +		 || fieldPtr == NULL +	    || TclGetIntFromObj(interp, fieldPtr, &(fields.iso8601Year)) != TCL_OK +	    || Tcl_DictObjGet(interp, dict, literals[LIT_ISO8601WEEK], &fieldPtr) != TCL_OK +		 || fieldPtr == NULL +	    || TclGetIntFromObj(interp, fieldPtr, &(fields.iso8601Week)) != TCL_OK +	    || Tcl_DictObjGet(interp, dict, literals[LIT_DAYOFWEEK], &fieldPtr) != TCL_OK +		 || fieldPtr == NULL +	    || TclGetIntFromObj(interp, fieldPtr, &(fields.dayOfWeek)) != TCL_OK +	    || TclGetIntFromObj(interp, objv[2], &changeover) != TCL_OK) { +    if (fieldPtr == NULL) +	Tcl_SetObjResult(interp, Tcl_NewStringObj("expected key(s) not found in dictionary", -1)); +	return TCL_ERROR; +    } +    fields.era = era; + +    /* +     * Get Julian day. +     */ + +    GetJulianDayFromEraYearWeekDay(&fields, changeover); + +    /* +     * Store Julian day in the dictionary - copy on write. +     */ + +    if (Tcl_IsShared(dict)) { +	dict = Tcl_DuplicateObj(dict); +	Tcl_IncrRefCount(dict); +	copied = 1; +    } +    status = Tcl_DictObjPut(interp, dict, literals[LIT_JULIANDAY], +	    Tcl_NewIntObj(fields.julianDay)); +    if (status == TCL_OK) { +	Tcl_SetObjResult(interp, dict); +    } +    if (copied) { +	Tcl_DecrRefCount(dict); +    } +    return status; +} + +/* + *---------------------------------------------------------------------- + * + * ConvertLocalToUTC -- + * + *	Converts a time (in a TclDateFields structure) from the local wall + *	clock to UTC. + * + * Results: + *	Returns a standard Tcl result. + * + * Side effects: + *	Populates the 'seconds' field if successful; stores an error message + *	in the interpreter result on failure. + * + *---------------------------------------------------------------------- + */ + +static int +ConvertLocalToUTC( +    Tcl_Interp *interp,		/* Tcl interpreter */ +    TclDateFields *fields,	/* Fields of the time */ +    Tcl_Obj *tzdata,		/* Time zone data */ +    int changeover)		/* Julian Day of the Gregorian transition */ +{ +    int rowc;			/* Number of rows in tzdata */ +    Tcl_Obj **rowv;		/* Pointers to the rows */ + +    /* +     * Unpack the tz data. +     */ + +    if (TclListObjGetElements(interp, tzdata, &rowc, &rowv) != TCL_OK) { +	return TCL_ERROR; +    } + +    /* +     * Special case: If the time zone is :localtime, the tzdata will be empty. +     * Use 'mktime' to convert the time to local +     */ + +    if (rowc == 0) { +	return ConvertLocalToUTCUsingC(interp, fields, changeover); +    } else { +	return ConvertLocalToUTCUsingTable(interp, fields, rowc, rowv); +    } +} + +/* + *---------------------------------------------------------------------- + * + * ConvertLocalToUTCUsingTable -- + * + *	Converts a time (in a TclDateFields structure) from local time in a + *	given time zone to UTC. + * + * Results: + *	Returns a standard Tcl result. + * + * Side effects: + *	Stores an error message in the interpreter if an error occurs; if + *	successful, stores the 'seconds' field in 'fields. + * + *---------------------------------------------------------------------- + */ + +static int +ConvertLocalToUTCUsingTable( +    Tcl_Interp *interp,		/* Tcl interpreter */ +    TclDateFields *fields,	/* Time to convert, with 'seconds' filled in */ +    int rowc,			/* Number of points at which time changes */ +    Tcl_Obj *const rowv[])	/* Points at which time changes */ +{ +    Tcl_Obj *row; +    int cellc; +    Tcl_Obj **cellv; +    int have[8]; +    int nHave = 0; +    int i; +    int found; + +    /* +     * Perform an initial lookup assuming that local == UTC, and locate the +     * last time conversion prior to that time. Get the offset from that row, +     * and look up again. Continue until we find an offset that we found +     * before. This definition, rather than "the same offset" ensures that we +     * don't enter an endless loop, as would otherwise happen when trying to +     * convert a non-existent time such as 02:30 during the US Spring Daylight +     * Saving Time transition. +     */ + +    found = 0; +    fields->tzOffset = 0; +    fields->seconds = fields->localSeconds; +    while (!found) { +	row = LookupLastTransition(interp, fields->seconds, rowc, rowv); +	if ((row == NULL) +		|| TclListObjGetElements(interp, row, &cellc, +		    &cellv) != TCL_OK +		|| TclGetIntFromObj(interp, cellv[1], +		    &fields->tzOffset) != TCL_OK) { +	    return TCL_ERROR; +	} +	found = 0; +	for (i = 0; !found && i < nHave; ++i) { +	    if (have[i] == fields->tzOffset) { +		found = 1; +		break;  	    } -	    if (forceMilli) { -		/* -		 * We can enforce at least millisecond granularity -		 */ -		Tcl_Time time; -		Tcl_GetTime(&time); -		Tcl_SetLongObj(resultPtr, -			(long) (time.sec*1000 + time.usec/1000)); -	    } else { -		Tcl_SetLongObj(resultPtr, (long) TclpGetClicks()); +	} +	if (!found) { +	    if (nHave == 8) { +		Tcl_Panic("loop in ConvertLocalToUTCUsingTable");  	    } -	    return TCL_OK; +	    have[nHave++] = fields->tzOffset;  	} +	fields->seconds = fields->localSeconds - fields->tzOffset; +    } +    fields->tzOffset = have[i]; +    fields->seconds = fields->localSeconds - fields->tzOffset; +    return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * ConvertLocalToUTCUsingC -- + * + *	Converts a time from local wall clock to UTC when the local time zone + *	cannot be determined. Uses 'mktime' to do the job. + * + * Results: + *	Returns a standard Tcl result. + * + * Side effects: + *	Stores an error message in the interpreter if an error occurs; if + *	successful, stores the 'seconds' field in 'fields. + * + *---------------------------------------------------------------------- + */ -	case COMMAND_FORMAT:			/* format */ -	    if ((objc < 3) || (objc > 7)) { -		wrongFmtArgs: -		Tcl_WrongNumArgs(interp, 2, objv, -			"clockval ?-format string? ?-gmt boolean?"); -		return TCL_ERROR; -	    } +static int +ConvertLocalToUTCUsingC( +    Tcl_Interp *interp,		/* Tcl interpreter */ +    TclDateFields *fields,	/* Time to convert, with 'seconds' filled in */ +    int changeover)		/* Julian Day of the Gregorian transition */ +{ +    struct tm timeVal; +    int localErrno; +    int secondOfDay; +    Tcl_WideInt jsec; -	    if (Tcl_GetLongFromObj(interp, objv[2], (long*) &clockVal) -		    != TCL_OK) { -		return TCL_ERROR; -	    } -     -	    objPtr = objv+3; -	    objc -= 3; -	    while (objc > 1) { -		if (Tcl_GetIndexFromObj(interp, objPtr[0], formatSwitches, -			"switch", 0, &index) != TCL_OK) { -		    return TCL_ERROR; -		} -		switch (index) { -		    case 0:		/* -format */ -			format = Tcl_GetStringFromObj(objPtr[1], &dummy); -			break; -		    case 1:		/* -gmt */ -			if (Tcl_GetBooleanFromObj(interp, objPtr[1], -				&useGMT) != TCL_OK) { -			    return TCL_ERROR; -			} -			break; -		} -		objPtr += 2; -		objc -= 2; -	    } -	    if (objc != 0) { -		goto wrongFmtArgs; -	    } -	    return FormatClock(interp, (unsigned long) clockVal, useGMT, -		    format); - -	case COMMAND_SCAN:			/* scan */ -	    if ((objc < 3) || (objc > 7)) { -		wrongScanArgs: -		Tcl_WrongNumArgs(interp, 2, objv, -			"dateString ?-base clockValue? ?-gmt boolean?"); -		return TCL_ERROR; -	    } +    /* +     * Convert the given time to a date. +     */ -	    objPtr = objv+3; -	    objc -= 3; -	    while (objc > 1) { -		if (Tcl_GetIndexFromObj(interp, objPtr[0], scanSwitches, -			"switch", 0, &index) != TCL_OK) { -		    return TCL_ERROR; -		} -		switch (index) { -		    case 0:		/* -base */ -			baseObjPtr = objPtr[1]; -			break; -		    case 1:		/* -gmt */ -			if (Tcl_GetBooleanFromObj(interp, objPtr[1], -				&useGMT) != TCL_OK) { -			    return TCL_ERROR; -			} -			break; -		} -		objPtr += 2; -		objc -= 2; -	    } -	    if (objc != 0) { -		goto wrongScanArgs; -	    } +    jsec = fields->localSeconds + JULIAN_SEC_POSIX_EPOCH; +    fields->julianDay = (int) (jsec / SECONDS_PER_DAY); +    secondOfDay = (int)(jsec % SECONDS_PER_DAY); +    if (secondOfDay < 0) { +	secondOfDay += SECONDS_PER_DAY; +	fields->julianDay--; +    } +    GetGregorianEraYearDay(fields, changeover); +    GetMonthDay(fields); -	    if (baseObjPtr != NULL) { -		if (Tcl_GetLongFromObj(interp, baseObjPtr, -			(long*) &baseClock) != TCL_OK) { -		    return TCL_ERROR; -		} -	    } else { -		baseClock = TclpGetSeconds(); -	    } +    /* +     * Convert the date/time to a 'struct tm'. +     */ -	    if (useGMT) { -		zone = -50000; /* Force GMT */ -	    } else { -		zone = TclpGetTimeZone((unsigned long) baseClock); -	    } +    timeVal.tm_year = fields->year - 1900; +    timeVal.tm_mon = fields->month - 1; +    timeVal.tm_mday = fields->dayOfMonth; +    timeVal.tm_hour = (secondOfDay / 3600) % 24; +    timeVal.tm_min = (secondOfDay / 60) % 60; +    timeVal.tm_sec = secondOfDay % 60; +    timeVal.tm_isdst = -1; +    timeVal.tm_wday = -1; +    timeVal.tm_yday = -1; -	    scanStr = Tcl_GetStringFromObj(objv[2], &dummy); -	    Tcl_MutexLock(&clockMutex); -	    if (TclGetDate(scanStr, (unsigned long) baseClock, zone, -		    (unsigned long *) &clockVal) < 0) { -		Tcl_MutexUnlock(&clockMutex); -		Tcl_AppendStringsToObj(resultPtr, -			"unable to convert date-time string \"", -			scanStr, "\"", (char *) NULL); -		return TCL_ERROR; -	    } -	    Tcl_MutexUnlock(&clockMutex); +    /* +     * Get local time. It is rumored that mktime is not thread safe on some +     * platforms, so seize a mutex before attempting this. +     */ -	    Tcl_SetLongObj(resultPtr, (long) clockVal); -	    return TCL_OK; +    TzsetIfNecessary(); +    Tcl_MutexLock(&clockMutex); +    errno = 0; +    fields->seconds = (Tcl_WideInt) mktime(&timeVal); +    localErrno = errno; +    Tcl_MutexUnlock(&clockMutex); -	case COMMAND_SECONDS:			/* seconds */ -	    if (objc != 2) { -		Tcl_WrongNumArgs(interp, 2, objv, NULL); -		return TCL_ERROR; -	    } -	    Tcl_SetLongObj(resultPtr, (long) TclpGetSeconds()); -	    return TCL_OK; -	default: -	    return TCL_ERROR;	/* Should never be reached. */ +    /* +     * If conversion fails, report an error. +     */ + +    if (localErrno != 0 +	    || (fields->seconds == -1 && timeVal.tm_yday == -1)) { +	Tcl_SetObjResult(interp, Tcl_NewStringObj( +		"time value too large/small to represent", -1)); +	return TCL_ERROR;      } +    return TCL_OK;  }  /* - *----------------------------------------------------------------------------- + *----------------------------------------------------------------------   * - * FormatClock -- + * ConvertUTCToLocal --   * - *      Formats a time value based on seconds into a human readable - *	string. + *	Converts a time (in a TclDateFields structure) from UTC to local time.   *   * Results: - *      Standard Tcl result. + *	Returns a standard Tcl result.   *   * Side effects: - *      None. + *	Populates the 'tzName' and 'tzOffset' fields.   * - *----------------------------------------------------------------------------- + *----------------------------------------------------------------------   */  static int -FormatClock(interp, clockVal, useGMT, format) -    Tcl_Interp *interp;			/* Current interpreter. */ -    unsigned long clockVal;	       	/* Time in seconds. */ -    int useGMT;				/* Boolean */ -    char *format;			/* Format string */ +ConvertUTCToLocal( +    Tcl_Interp *interp,		/* Tcl interpreter */ +    TclDateFields *fields,	/* Fields of the time */ +    Tcl_Obj *tzdata,		/* Time zone data */ +    int changeover)		/* Julian Day of the Gregorian transition */  { -    struct tm *timeDataPtr; -    Tcl_DString buffer, uniBuffer; -    int bufSize; -    char *p; -    int result; -    time_t tclockVal; -#ifndef HAVE_TM_ZONE -    int savedTimeZone = 0;	/* lint. */ -    char *savedTZEnv = NULL;	/* lint. */ -#endif +    int rowc;			/* Number of rows in tzdata */ +    Tcl_Obj **rowv;		/* Pointers to the rows */ -#ifdef HAVE_TZSET      /* -     * Some systems forgot to call tzset in localtime, make sure its done. +     * Unpack the tz data.       */ -    static int  calledTzset = 0; -    Tcl_MutexLock(&clockMutex); -    if (!calledTzset) { -        tzset(); -        calledTzset = 1; +    if (TclListObjGetElements(interp, tzdata, &rowc, &rowv) != TCL_OK) { +	return TCL_ERROR;      } -    Tcl_MutexUnlock(&clockMutex); -#endif      /* -     * If the user gave us -format "", just return now +     * Special case: If the time zone is :localtime, the tzdata will be empty. +     * Use 'localtime' to convert the time to local +     */ + +    if (rowc == 0) { +	return ConvertUTCToLocalUsingC(interp, fields, changeover); +    } else { +	return ConvertUTCToLocalUsingTable(interp, fields, rowc, rowv); +    } +} + +/* + *---------------------------------------------------------------------- + * + * ConvertUTCToLocalUsingTable -- + * + *	Converts UTC to local time, given a table of transition points + * + * Results: + *	Returns a standard Tcl result + * + * Side effects: + *	On success, fills fields->tzName, fields->tzOffset and + *	fields->localSeconds. On failure, places an error message in the + *	interpreter result. + * + *---------------------------------------------------------------------- + */ + +static int +ConvertUTCToLocalUsingTable( +    Tcl_Interp *interp,		/* Tcl interpreter */ +    TclDateFields *fields,	/* Fields of the date */ +    int rowc,			/* Number of rows in the conversion table +				 * (>= 1) */ +    Tcl_Obj *const rowv[])	/* Rows of the conversion table */ +{ +    Tcl_Obj *row;		/* Row containing the current information */ +    int cellc;			/* Count of cells in the row (must be 4) */ +    Tcl_Obj **cellv;		/* Pointers to the cells */ + +    /* +     * Look up the nearest transition time. +     */ + +    row = LookupLastTransition(interp, fields->seconds, rowc, rowv); +    if (row == NULL || +	    TclListObjGetElements(interp, row, &cellc, &cellv) != TCL_OK || +	    TclGetIntFromObj(interp, cellv[1], &fields->tzOffset) != TCL_OK) { +	return TCL_ERROR; +    } + +    /* +     * Convert the time. +     */ + +    fields->tzName = cellv[3]; +    Tcl_IncrRefCount(fields->tzName); +    fields->localSeconds = fields->seconds + fields->tzOffset; +    return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * ConvertUTCToLocalUsingC -- + * + *	Converts UTC to localtime in cases where the local time zone is not + *	determinable, using the C 'localtime' function to do it. + * + * Results: + *	Returns a standard Tcl result. + * + * Side effects: + *	On success, fills fields->tzName, fields->tzOffset and + *	fields->localSeconds. On failure, places an error message in the + *	interpreter result. + * + *---------------------------------------------------------------------- + */ + +static int +ConvertUTCToLocalUsingC( +    Tcl_Interp *interp,		/* Tcl interpreter */ +    TclDateFields *fields,	/* Time to convert, with 'seconds' filled in */ +    int changeover)		/* Julian Day of the Gregorian transition */ +{ +    time_t tock; +    struct tm *timeVal;		/* Time after conversion */ +    int diff;			/* Time zone diff local-Greenwich */ +    char buffer[8];		/* Buffer for time zone name */ + +    /* +     * Use 'localtime' to determine local year, month, day, time of day. +     */ + +    tock = (time_t) fields->seconds; +    if ((Tcl_WideInt) tock != fields->seconds) { +	Tcl_SetObjResult(interp, Tcl_NewStringObj( +		"number too large to represent as a Posix time", -1)); +	Tcl_SetErrorCode(interp, "CLOCK", "argTooLarge", NULL); +	return TCL_ERROR; +    } +    TzsetIfNecessary(); +    timeVal = ThreadSafeLocalTime(&tock); +    if (timeVal == NULL) { +	Tcl_SetObjResult(interp, Tcl_NewStringObj( +		"localtime failed (clock value may be too " +		"large/small to represent)", -1)); +	Tcl_SetErrorCode(interp, "CLOCK", "localtimeFailed", NULL); +	return TCL_ERROR; +    } + +    /* +     * Fill in the date in 'fields' and use it to derive Julian Day. +     */ + +    fields->era = CE; +    fields->year = timeVal->tm_year + 1900; +    fields->month = timeVal->tm_mon + 1; +    fields->dayOfMonth = timeVal->tm_mday; +    GetJulianDayFromEraYearMonthDay(fields, changeover); + +    /* +     * Convert that value to seconds. +     */ + +    fields->localSeconds = (((fields->julianDay * (Tcl_WideInt) 24 +	    + timeVal->tm_hour) * 60 + timeVal->tm_min) * 60 +	    + timeVal->tm_sec) - JULIAN_SEC_POSIX_EPOCH; + +    /* +     * Determine a time zone offset and name; just use +hhmm for the name.       */ -    if (*format == '\0') { -	return TCL_OK; + +    diff = (int) (fields->localSeconds - fields->seconds); +    fields->tzOffset = diff; +    if (diff < 0) { +	*buffer = '-'; +	diff = -diff; +    } else { +	*buffer = '+';      } +    sprintf(buffer+1, "%02d", diff / 3600); +    diff %= 3600; +    sprintf(buffer+3, "%02d", diff / 60); +    diff %= 60; +    if (diff > 0) { +	sprintf(buffer+5, "%02d", diff); +    } +    fields->tzName = Tcl_NewStringObj(buffer, -1); +    Tcl_IncrRefCount(fields->tzName); +    return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * LookupLastTransition -- + * + *	Given a UTC time and a tzdata array, looks up the last transition on + *	or before the given time. + * + * Results: + *	Returns a pointer to the row, or NULL if an error occurs. + * + *---------------------------------------------------------------------- + */ + +static Tcl_Obj * +LookupLastTransition( +    Tcl_Interp *interp,		/* Interpreter for error messages */ +    Tcl_WideInt tick,		/* Time from the epoch */ +    int rowc,			/* Number of rows of tzdata */ +    Tcl_Obj *const *rowv)	/* Rows in tzdata */ +{ +    int l; +    int u; +    Tcl_Obj *compObj; +    Tcl_WideInt compVal; -#ifndef HAVE_TM_ZONE      /* -     * This is a kludge for systems not having the timezone string in -     * struct tm.  No matter what was specified, they use the local -     * timezone string. +     * Examine the first row to make sure we're in bounds.       */ -    if (useGMT) { -        CONST char *varValue; +    if (Tcl_ListObjIndex(interp, rowv[0], 0, &compObj) != TCL_OK +	    || Tcl_GetWideIntFromObj(interp, compObj, &compVal) != TCL_OK) { +	return NULL; +    } -        varValue = Tcl_GetVar2(interp, "env", "TZ", TCL_GLOBAL_ONLY); -        if (varValue != NULL) { -	    savedTZEnv = strcpy(ckalloc(strlen(varValue) + 1), varValue); -        } else { -            savedTZEnv = NULL; +    /* +     * Bizarre case - first row doesn't begin at MIN_WIDE_INT. Return it +     * anyway. +     */ + +    if (tick < compVal) { +	return rowv[0]; +    } + +    /* +     * Binary-search to find the transition. +     */ + +    l = 0; +    u = rowc-1; +    while (l < u) { +	int m = (l + u + 1) / 2; + +	if (Tcl_ListObjIndex(interp, rowv[m], 0, &compObj) != TCL_OK || +		Tcl_GetWideIntFromObj(interp, compObj, &compVal) != TCL_OK) { +	    return NULL; +	} +	if (tick >= compVal) { +	    l = m; +	} else { +	    u = m-1;  	} -        Tcl_SetVar2(interp, "env", "TZ", "GMT", TCL_GLOBAL_ONLY); -        savedTimeZone = timezone; -        timezone = 0; -        tzset();      } -#endif +    return rowv[l]; +} + +/* + *---------------------------------------------------------------------- + * + * GetYearWeekDay -- + * + *	Given a date with Julian Calendar Day, compute the year, week, and day + *	in the ISO8601 calendar. + * + * Results: + *	None. + * + * Side effects: + *	Stores 'iso8601Year', 'iso8601Week' and 'dayOfWeek' in the date + *	fields. + * + *---------------------------------------------------------------------- + */ + +static void +GetYearWeekDay( +    TclDateFields *fields,	/* Date to convert, must have 'julianDay' */ +    int changeover)		/* Julian Day Number of the Gregorian +				 * transition */ +{ +    TclDateFields temp; +    int dayOfFiscalYear; + +    /* +     * Find the given date, minus three days, plus one year. That date's +     * iso8601 year is an upper bound on the ISO8601 year of the given date. +     */ + +    temp.julianDay = fields->julianDay - 3; +    GetGregorianEraYearDay(&temp, changeover); +    if (temp.era == BCE) { +	temp.iso8601Year = temp.year - 1; +    } else { +	temp.iso8601Year = temp.year + 1; +    } +    temp.iso8601Week = 1; +    temp.dayOfWeek = 1; +    GetJulianDayFromEraYearWeekDay(&temp, changeover); -    tclockVal = clockVal; -    timeDataPtr = TclpGetDate((TclpTime_t) &tclockVal, useGMT); -          /* -     * Make a guess at the upper limit on the substituted string size -     * based on the number of percents in the string. +     * temp.julianDay is now the start of an ISO8601 year, either the one +     * corresponding to the given date, or the one after. If we guessed high, +     * move one year earlier       */ -    for (bufSize = 1, p = format; *p != '\0'; p++) { -	if (*p == '%') { -	    bufSize += 40; +    if (fields->julianDay < temp.julianDay) { +	if (temp.era == BCE) { +	    temp.iso8601Year += 1;  	} else { -	    bufSize++; +	    temp.iso8601Year -= 1; +	} +	GetJulianDayFromEraYearWeekDay(&temp, changeover); +    } + +    fields->iso8601Year = temp.iso8601Year; +    dayOfFiscalYear = fields->julianDay - temp.julianDay; +    fields->iso8601Week = (dayOfFiscalYear / 7) + 1; +    fields->dayOfWeek = (dayOfFiscalYear + 1) % 7; +    if (fields->dayOfWeek < 1) { +	fields->dayOfWeek += 7; +    } +} + +/* + *---------------------------------------------------------------------- + * + * GetGregorianEraYearDay -- + * + *	Given a Julian Day Number, extracts the year and day of the year and + *	puts them into TclDateFields, along with the era (BCE or CE) and a + *	flag indicating whether the date is Gregorian or Julian. + * + * Results: + *	None. + * + * Side effects: + *	Stores 'era', 'gregorian', 'year', and 'dayOfYear'. + * + *---------------------------------------------------------------------- + */ + +static void +GetGregorianEraYearDay( +    TclDateFields *fields,	/* Date fields containing 'julianDay' */ +    int changeover)		/* Gregorian transition date */ +{ +    int jday = fields->julianDay; +    int day; +    int year; +    int n; + +    if (jday >= changeover) { +	/* +	 * Gregorian calendar. +	 */ + +	fields->gregorian = 1; +	year = 1; + +	/* +	 * n = Number of 400-year cycles since 1 January, 1 CE in the +	 * proleptic Gregorian calendar. day = remaining days. +	 */ + +	day = jday - JDAY_1_JAN_1_CE_GREGORIAN; +	n = day / FOUR_CENTURIES; +	day %= FOUR_CENTURIES; +	if (day < 0) { +	    day += FOUR_CENTURIES; +	    n--; +	} +	year += 400 * n; + +	/* +	 * n = number of centuries since the start of (year); +	 * day = remaining days +	 */ + +	n = day / ONE_CENTURY_GREGORIAN; +	day %= ONE_CENTURY_GREGORIAN; +	if (n > 3) { +	    /* +	     * 31 December in the last year of a 400-year cycle. +	     */ + +	    n = 3; +	    day += ONE_CENTURY_GREGORIAN;  	} +	year += 100 * n; +    } else { +	/* +	 * Julian calendar. +	 */ + +	fields->gregorian = 0; +	year = 1; +	day = jday - JDAY_1_JAN_1_CE_JULIAN; +    } + +    /* +     * n = number of 4-year cycles; days = remaining days. +     */ + +    n = day / FOUR_YEARS; +    day %= FOUR_YEARS; +    if (day < 0) { +	day += FOUR_YEARS; +	n--; +    } +    year += 4 * n; + +    /* +     * n = number of years; days = remaining days. +     */ + +    n = day / ONE_YEAR; +    day %= ONE_YEAR; +    if (n > 3) { +	/* +	 * 31 December of a leap year. +	 */ + +	n = 3; +	day += 365; +    } +    year += n; + +    /* +     * store era/year/day back into fields. +     */ + +    if (year <= 0) { +	fields->era = BCE; +	fields->year = 1 - year; +    } else { +	fields->era = CE; +	fields->year = year; +    } +    fields->dayOfYear = day + 1; +} + +/* + *---------------------------------------------------------------------- + * + * GetMonthDay -- + * + *	Given a date as year and day-of-year, find month and day. + * + * Results: + *	None. + * + * Side effects: + *	Stores 'month' and 'dayOfMonth' in the 'fields' structure. + * + *---------------------------------------------------------------------- + */ + +static void +GetMonthDay( +    TclDateFields *fields)	/* Date to convert */ +{ +    int day = fields->dayOfYear; +    int month; +    const int *h = hath[IsGregorianLeapYear(fields)]; + +    for (month = 0; month < 12 && day > h[month]; ++month) { +	day -= h[month]; +    } +    fields->month = month+1; +    fields->dayOfMonth = day; +} + +/* + *---------------------------------------------------------------------- + * + * GetJulianDayFromEraYearWeekDay -- + * + *	Given a TclDateFields structure containing era, ISO8601 year, ISO8601 + *	week, and day of week, computes the Julian Day Number. + * + * Results: + *	None. + * + * Side effects: + *	Stores 'julianDay' in the fields. + * + *---------------------------------------------------------------------- + */ + +static void +GetJulianDayFromEraYearWeekDay( +    TclDateFields *fields,	/* Date to convert */ +    int changeover)		/* Julian Day Number of the Gregorian +				 * transition */ +{ +    int firstMonday;		/* Julian day number of week 1, day 1 in the +				 * given year */ +    TclDateFields firstWeek; + +    /* +     * Find January 4 in the ISO8601 year, which will always be in week 1. +     */ + +    firstWeek.era = fields->era; +    firstWeek.year = fields->iso8601Year; +    firstWeek.month = 1; +    firstWeek.dayOfMonth = 4; +    GetJulianDayFromEraYearMonthDay(&firstWeek, changeover); + +    /* +     * Find Monday of week 1. +     */ + +    firstMonday = WeekdayOnOrBefore(1, firstWeek.julianDay); + +    /* +     * Advance to the given week and day. +     */ + +    fields->julianDay = firstMonday + 7 * (fields->iso8601Week - 1) +	    + fields->dayOfWeek - 1; +} + +/* + *---------------------------------------------------------------------- + * + * GetJulianDayFromEraYearMonthDay -- + * + *	Given era, year, month, and dayOfMonth (in TclDateFields), and the + *	Gregorian transition date, computes the Julian Day Number. + * + * Results: + *	None. + * + * Side effects: + *	Stores day number in 'julianDay' + * + *---------------------------------------------------------------------- + */ + +static void +GetJulianDayFromEraYearMonthDay( +    TclDateFields *fields,	/* Date to convert */ +    int changeover)		/* Gregorian transition date as a Julian Day */ +{ +    int year, ym1, month, mm1, q, r, ym1o4, ym1o100, ym1o400; + +    if (fields->era == BCE) { +	year = 1 - fields->year; +    } else { +	year = fields->year; +    } + +    /* +     * Reduce month modulo 12. +     */ + +    month = fields->month; +    mm1 = month - 1; +    q = mm1 / 12; +    r = (mm1 % 12); +    if (r < 0) { +	r += 12; +	q -= 1; +    } +    year += q; +    month = r + 1; +    ym1 = year - 1; + +    /* +     * Adjust the year after reducing the month. +     */ + +    fields->gregorian = 1; +    if (year < 1) { +	fields->era = BCE; +	fields->year = 1-year; +    } else { +	fields->era = CE; +	fields->year = year; +    } + +    /* +     * Try an initial conversion in the Gregorian calendar. +     */ + +    ym1o4 = ym1 / 4; +    if (ym1 % 4 < 0) { +	ym1o4--; +    } +    ym1o100 = ym1 / 100; +    if (ym1 % 100 < 0) { +	ym1o100--; +    } +    ym1o400 = ym1 / 400; +    if (ym1 % 400 < 0) { +	ym1o400--; +    } +    fields->julianDay = JDAY_1_JAN_1_CE_GREGORIAN - 1 +	    + fields->dayOfMonth +	    + daysInPriorMonths[IsGregorianLeapYear(fields)][month - 1] +	    + (ONE_YEAR * ym1) +	    + ym1o4 +	    - ym1o100 +	    + ym1o400; + +    /* +     * If the resulting date is before the Gregorian changeover, convert in +     * the Julian calendar instead. +     */ + +    if (fields->julianDay < changeover) { +	fields->gregorian = 0; +	fields->julianDay = JDAY_1_JAN_1_CE_JULIAN - 1 +		+ fields->dayOfMonth +		+ daysInPriorMonths[year%4 == 0][month - 1] +		+ (365 * ym1) +		+ ym1o4; +    } +} + +/* + *---------------------------------------------------------------------- + * + * IsGregorianLeapYear -- + * + *	Tests whether a given year is a leap year, in either Julian or + *	Gregorian calendar. + * + * Results: + *	Returns 1 for a leap year, 0 otherwise. + * + *---------------------------------------------------------------------- + */ + +static int +IsGregorianLeapYear( +    TclDateFields *fields)	/* Date to test */ +{ +    int year; + +    if (fields->era == BCE) { +	year = 1 - fields->year; +    } else { +	year = fields->year;      } -    Tcl_DStringInit(&buffer); -    Tcl_DStringSetLength(&buffer, bufSize); +    if (year%4 != 0) { +	return 0; +    } else if (!(fields->gregorian)) { +	return 1; +    } else if (year%400 == 0) { +	return 1; +    } else if (year%100 == 0) { +	return 0; +    } else { +	return 1; +    } +} + +/* + *---------------------------------------------------------------------- + * + * WeekdayOnOrBefore -- + * + *	Finds the Julian Day Number of a given day of the week that falls on + *	or before a given date, expressed as Julian Day Number. + * + * Results: + *	Returns the Julian Day Number + * + *---------------------------------------------------------------------- + */ + +static int +WeekdayOnOrBefore( +    int dayOfWeek,		/* Day of week; Sunday == 0 or 7 */ +    int julianDay)		/* Reference date */ +{ +    int k = (dayOfWeek + 6) % 7; +    if (k < 0) { +	k += 7; +    } +    return julianDay - ((julianDay - k) % 7); +} + +/* + *---------------------------------------------------------------------- + * + * ClockGetenvObjCmd -- + * + *	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 +ClockGetenvObjCmd( +    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 = TclGetString(objv[1]); +    varValue = getenv(varName); +    if (varValue == NULL) { +	varValue = ""; +    } +    Tcl_SetObjResult(interp, Tcl_NewStringObj(varValue, -1)); +    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( +    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 = Tcl_GetThreadData(&tmKey, (int) sizeof(struct tm)); +#ifdef HAVE_LOCALTIME_R +    localtime_r(timePtr, tmPtr); +#else +    struct tm *sysTmPtr;      Tcl_MutexLock(&clockMutex); -    result = TclpStrftime(buffer.string, (unsigned int) bufSize, format, -	    timeDataPtr); +    sysTmPtr = localtime(timePtr); +    if (sysTmPtr == NULL) { +	Tcl_MutexUnlock(&clockMutex); +	return NULL; +    } +    memcpy(tmPtr, localtime(timePtr), sizeof(struct tm));      Tcl_MutexUnlock(&clockMutex); +#endif +    return tmPtr; +} + +/*---------------------------------------------------------------------- + * + * ClockClicksObjCmd -- + * + *	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 +ClockClicksObjCmd( +    ClientData clientData,	/* Client data is unused */ +    Tcl_Interp *interp,		/* Tcl interpreter */ +    int objc,			/* Parameter count */ +    Tcl_Obj *const *objv)	/* Parameter values */ +{ +    static const char *const clicksSwitches[] = { +	"-milliseconds", "-microseconds", NULL +    }; +    enum ClicksSwitch { +	CLICKS_MILLIS, CLICKS_MICROS, CLICKS_NATIVE +    }; +    int index = CLICKS_NATIVE; +    Tcl_Time now; +    Tcl_WideInt clicks = 0; -#ifndef HAVE_TM_ZONE -    if (useGMT) { -        if (savedTZEnv != NULL) { -            Tcl_SetVar2(interp, "env", "TZ", savedTZEnv, TCL_GLOBAL_ONLY); -            ckfree(savedTZEnv); -        } else { -            Tcl_UnsetVar2(interp, "env", "TZ", TCL_GLOBAL_ONLY); -        } -        timezone = savedTimeZone; -        tzset(); +    switch (objc) { +    case 1: +	break; +    case 2: +	if (Tcl_GetIndexFromObj(interp, objv[1], clicksSwitches, "switch", 0, +		&index) != TCL_OK) { +	    return TCL_ERROR; +	} +	break; +    default: +	Tcl_WrongNumArgs(interp, 1, objv, "?-switch?"); +	return TCL_ERROR;      } + +    switch (index) { +    case CLICKS_MILLIS: +	Tcl_GetTime(&now); +	clicks = (Tcl_WideInt) now.sec * 1000 + now.usec / 1000; +	break; +    case CLICKS_NATIVE: +#ifdef TCL_WIDE_CLICKS +	clicks = TclpGetWideClicks(); +#else +	clicks = (Tcl_WideInt) TclpGetClicks();  #endif +	break; +    case CLICKS_MICROS: +	Tcl_GetTime(&now); +	clicks = ((Tcl_WideInt) now.sec * 1000000) + now.usec; +	break; +    } -    if (result == 0) { -	/* -	 * A zero return is the error case (can also mean the strftime -	 * didn't get enough space to write into).  We know it doesn't -	 * mean that we wrote zero chars because the check for an empty -	 * format string is above. -	 */ -	Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), -		"bad format string \"", format, "\"", (char *) NULL); +    Tcl_SetObjResult(interp, Tcl_NewWideIntObj(clicks)); +    return TCL_OK; +} + +/*---------------------------------------------------------------------- + * + * ClockMillisecondsObjCmd - + * + *	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 +ClockMillisecondsObjCmd( +    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; +} + +/*---------------------------------------------------------------------- + * + * ClockMicrosecondsObjCmd - + * + *	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 +ClockMicrosecondsObjCmd( +    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; +} + +/* + *----------------------------------------------------------------------------- + * + * ClockParseformatargsObjCmd -- + * + *	Parses the arguments for [clock format]. + * + * Results: + *	Returns a standard Tcl result, whose value is a four-element list + *	comprising the time format, the locale, and the timezone. + * + * This function exists because the loop that parses the [clock format] + * options is a known performance "hot spot", and is implemented in an effort + * to speed that particular code up. + * + *----------------------------------------------------------------------------- + */ + +static int +ClockParseformatargsObjCmd( +    ClientData clientData,	/* Client data containing literal pool */ +    Tcl_Interp *interp,		/* Tcl interpreter */ +    int objc,			/* Parameter count */ +    Tcl_Obj *const objv[])	/* Parameter vector */ +{ +    ClockClientData *dataPtr = clientData; +    Tcl_Obj **litPtr = dataPtr->literals; +    Tcl_Obj *results[3];	/* Format, locale and timezone */ +#define formatObj results[0] +#define localeObj results[1] +#define timezoneObj results[2] +    int gmtFlag = 0; +    static const char *const options[] = { /* Command line options expected */ +	"-format",	"-gmt",		"-locale", +	"-timezone",	NULL }; +    enum optionInd { +	CLOCK_FORMAT_FORMAT,	CLOCK_FORMAT_GMT,	CLOCK_FORMAT_LOCALE, +	CLOCK_FORMAT_TIMEZONE +    }; +    int optionIndex;		/* Index of an option. */ +    int saw = 0;		/* Flag == 1 if option was seen already. */ +    Tcl_WideInt clockVal;	/* Clock value - just used to parse. */ +    int i; + +    /* +     * Args consist of a time followed by keyword-value pairs. +     */ + +    if (objc < 2 || (objc % 2) != 0) { +	Tcl_WrongNumArgs(interp, 0, objv, +		"clock format clockval ?-format string? " +		"?-gmt boolean? ?-locale LOCALE? ?-timezone ZONE?"); +	Tcl_SetErrorCode(interp, "CLOCK", "wrongNumArgs", NULL); +	return TCL_ERROR; +    } + +    /* +     * Extract values for the keywords. +     */ + +    formatObj = litPtr[LIT__DEFAULT_FORMAT]; +    localeObj = litPtr[LIT_C]; +    timezoneObj = litPtr[LIT__NIL]; +    for (i = 2; i < objc; i+=2) { +	if (Tcl_GetIndexFromObj(interp, objv[i], options, "switch", 0, +		&optionIndex) != TCL_OK) { +	    Tcl_SetErrorCode(interp, "CLOCK", "badSwitch", +		    Tcl_GetString(objv[i]), NULL); +	    return TCL_ERROR; +	} +	switch (optionIndex) { +	case CLOCK_FORMAT_FORMAT: +	    formatObj = objv[i+1]; +	    break; +	case CLOCK_FORMAT_GMT: +	    if (Tcl_GetBooleanFromObj(interp, objv[i+1], &gmtFlag) != TCL_OK){ +		return TCL_ERROR; +	    } +	    break; +	case CLOCK_FORMAT_LOCALE: +	    localeObj = objv[i+1]; +	    break; +	case CLOCK_FORMAT_TIMEZONE: +	    timezoneObj = objv[i+1]; +	    break; +	} +	saw |= 1 << optionIndex; +    }      /* -     * Convert the time to UTF from external encoding [Bug: 3345] +     * Check options.       */ -    Tcl_DStringInit(&uniBuffer); -    Tcl_ExternalToUtfDString(NULL, buffer.string, -1, &uniBuffer); -    Tcl_SetStringObj(Tcl_GetObjResult(interp), uniBuffer.string, -1); +    if (Tcl_GetWideIntFromObj(interp, objv[1], &clockVal) != TCL_OK) { +	return TCL_ERROR; +    } +    if ((saw & (1 << CLOCK_FORMAT_GMT)) +	    && (saw & (1 << CLOCK_FORMAT_TIMEZONE))) { +	Tcl_SetObjResult(interp, litPtr[LIT_CANNOT_USE_GMT_AND_TIMEZONE]); +	Tcl_SetErrorCode(interp, "CLOCK", "gmtWithTimezone", NULL); +	return TCL_ERROR; +    } +    if (gmtFlag) { +	timezoneObj = litPtr[LIT_GMT]; +    } -    Tcl_DStringFree(&uniBuffer); -    Tcl_DStringFree(&buffer); +    /* +     * Return options as a list. +     */ + +    Tcl_SetObjResult(interp, Tcl_NewListObj(3, results));      return TCL_OK; + +#undef timezoneObj +#undef localeObj +#undef formatObj  } + +/*---------------------------------------------------------------------- + * + * ClockSecondsObjCmd - + * + *	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 +ClockSecondsObjCmd( +    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(void) +{ +    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); +} + +/* + *---------------------------------------------------------------------- + * + * ClockDeleteCmdProc -- + * + *	Remove a reference to the clock client data, and clean up memory + *	when it's all gone. + * + * Results: + *	None. + * + *---------------------------------------------------------------------- + */ + +static void +ClockDeleteCmdProc( +    ClientData clientData)	/* Opaque pointer to the client data */ +{ +    ClockClientData *data = clientData; +    int i; + +    data->refCount--; +    if (data->refCount == 0) { +	for (i = 0; i < LIT__END; ++i) { +	    Tcl_DecrRefCount(data->literals[i]); +	} +	ckfree(data->literals); +	ckfree(data); +    } +} + +/* + * Local Variables: + * mode: c + * c-basic-offset: 4 + * fill-column: 78 + * End: + */ | 
