summaryrefslogtreecommitdiffstats
path: root/generic/tclClock.c
diff options
context:
space:
mode:
Diffstat (limited to 'generic/tclClock.c')
-rw-r--r--generic/tclClock.c2239
1 files changed, 1950 insertions, 289 deletions
diff --git a/generic/tclClock.c b/generic/tclClock.c
index 2c00a07..5b95ae6 100644
--- a/generic/tclClock.c
+++ b/generic/tclClock.c
@@ -1,23 +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.
+ * 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* 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)
@@ -26,366 +139,1914 @@ TCL_DECLARE_MUTEX(clockMutex)
* Function prototypes for local procedures in this file:
*/
-static int FormatClock _ANSI_ARGS_((Tcl_Interp *interp,
- Tcl_WideInt 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 }
+};
/*
- *-------------------------------------------------------------------------
+ *----------------------------------------------------------------------
*
- * Tcl_ClockObjCmd --
+ * TclClockInit --
*
- * This procedure is invoked to process the "clock" Tcl command.
- * See the user documentation for details on what it does.
+ * 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:
- * A standard Tcl result.
+ * None.
*
* Side effects:
- * See the user documentation.
+ * 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 NULL. */
+ 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 = (ClockClientData *) ckalloc(sizeof(ClockClientData));
+ data->refCount = 0;
+ data->literals = (Tcl_Obj**) 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.
+ */
+
+ strcpy(cmdName, "::tcl::clock::");
+#define TCL_CLOCK_PREFIX_LEN 14 /* == strlen("::tcl::clock::") */
+ 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);
+ }
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * ClockConvertlocaltoutcObjCmd --
+ *
+ * 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:
+ * Returns a standard Tcl result.
+ *
+ * Side effects:
+ * 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 = (ClockClientData*) 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;
- Tcl_WideInt baseClock, clockVal;
- long zone;
- Tcl_Obj *baseObjPtr = NULL;
- char *scanStr;
- int n;
-
- 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 = (ClockClientData*) 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;
}
- if (Tcl_GetIndexFromObj(interp, objv[1], switches, "option", 0, &index)
- != TCL_OK) {
+ /*
+ * fields.seconds could be an unsigned number that overflowed. Make
+ * sure that it isn't.
+ */
+
+ 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], &n);
- if ( ( n >= 2 )
- && ( strncmp( format, "-milliseconds",
- (unsigned int) n) == 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 = (ClockClientData*) 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
+ || Tcl_GetIndexFromObj(interp, fieldPtr, eras, "era", TCL_EXACT,
+ &era) != TCL_OK
+ || Tcl_DictObjGet(interp, dict, literals[LIT_YEAR],
+ &fieldPtr) != TCL_OK
+ || TclGetIntFromObj(interp, fieldPtr, &(fields.year)) != TCL_OK
+ || Tcl_DictObjGet(interp, dict, literals[LIT_MONTH],
+ &fieldPtr) != TCL_OK
+ || TclGetIntFromObj(interp, fieldPtr, &(fields.month)) != TCL_OK
+ || Tcl_DictObjGet(interp, dict, literals[LIT_DAYOFMONTH],
+ &fieldPtr) != TCL_OK
+ || TclGetIntFromObj(interp, fieldPtr,
+ &(fields.dayOfMonth)) != TCL_OK
+ || TclGetIntFromObj(interp, objv[2], &changeover) != TCL_OK) {
+ 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 = (ClockClientData*) 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
+ || Tcl_GetIndexFromObj(interp, fieldPtr, eras, "era", TCL_EXACT,
+ &era) != TCL_OK
+ || Tcl_DictObjGet(interp, dict, literals[LIT_ISO8601YEAR],
+ &fieldPtr) != TCL_OK
+ || TclGetIntFromObj(interp, fieldPtr,
+ &(fields.iso8601Year)) != TCL_OK
+ || Tcl_DictObjGet(interp, dict, literals[LIT_ISO8601WEEK],
+ &fieldPtr) != TCL_OK
+ || TclGetIntFromObj(interp, fieldPtr,
+ &(fields.iso8601Week)) != TCL_OK
+ || Tcl_DictObjGet(interp, dict, literals[LIT_DAYOFWEEK],
+ &fieldPtr) != TCL_OK
+ || TclGetIntFromObj(interp, fieldPtr,
+ &(fields.dayOfWeek)) != TCL_OK
+ || TclGetIntFromObj(interp, objv[2], &changeover) != TCL_OK) {
+ 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;
+ ++nHave;
}
+ 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_GetWideIntFromObj(interp, objv[2], &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, 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_GetWideIntFromObj(interp, baseObjPtr,
- &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(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, baseClock, zone,
- &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_SetWideIntObj(resultPtr, 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_SetResult(interp, "time value too large/small to represent",
+ TCL_STATIC);
+ 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. */
- Tcl_WideInt 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;
-#if !defined(HAVE_TM_ZONE) && !defined(WIN32)
- TIMEZONE_t 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_AppendResult(interp,
+ "number too large to represent as a Posix time", NULL);
+ Tcl_SetErrorCode(interp, "CLOCK", "argTooLarge", NULL);
+ return TCL_ERROR;
+ }
+ TzsetIfNecessary();
+ timeVal = ThreadSafeLocalTime(&tock);
+ if (timeVal == NULL) {
+ Tcl_AppendResult(interp,
+ "localtime failed (clock value may be too "
+ "large/small to represent)", NULL);
+ 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.
+ */
+
+ 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;
+
+ /*
+ * Examine the first row to make sure we're in bounds.
*/
- if (*format == '\0') {
- return TCL_OK;
+
+ if (Tcl_ListObjIndex(interp, rowv[0], 0, &compObj) != TCL_OK
+ || Tcl_GetWideIntFromObj(interp, compObj, &compVal) != TCL_OK) {
+ return NULL;
+ }
+
+ /*
+ * Bizarre case - first row doesn't begin at MIN_WIDE_INT. Return it
+ * anyway.
+ */
+
+ if (tick < compVal) {
+ return rowv[0];
}
-#if !defined(HAVE_TM_ZONE) && !defined(WIN32)
/*
- * 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. Since this kludge requires fiddling with the
- * TZ environment variable, it will mess up if done on multiple
- * threads at once. Protect it with a the clock mutex.
+ * Binary-search to find the transition.
*/
- Tcl_MutexLock( &clockMutex );
- if (useGMT) {
- CONST char *varValue;
+ l = 0;
+ u = rowc-1;
+ while (l < u) {
+ int m = (l + u + 1) / 2;
- varValue = Tcl_GetVar2(interp, "env", "TZ", TCL_GLOBAL_ONLY);
- if (varValue != NULL) {
- savedTZEnv = strcpy(ckalloc(strlen(varValue) + 1), varValue);
- } else {
- savedTZEnv = NULL;
+ 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", "GMT0", 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;
- tclockVal = (time_t) 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.
+ * 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.
*/
- for (bufSize = 1, p = format; *p != '\0'; p++) {
- if (*p == '%') {
- bufSize += 40;
- if (p[1] == 'c') {
- bufSize += 226;
- }
+ 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);
+
+ /*
+ * 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
+ */
+
+ if (fields->julianDay < temp.julianDay) {
+ if (temp.era == BCE) {
+ temp.iso8601Year += 1;
} else {
- bufSize++;
+ temp.iso8601Year -= 1;
}
+ GetJulianDayFromEraYearWeekDay(&temp, changeover);
}
- Tcl_DStringInit(&uniBuffer);
- Tcl_UtfToExternalDString(NULL, format, -1, &uniBuffer);
- Tcl_DStringInit(&buffer);
- Tcl_DStringSetLength(&buffer, bufSize);
- /* If we haven't locked the clock mutex up above, lock it now. */
+ 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'.
+ *
+ *----------------------------------------------------------------------
+ */
-#if defined(HAVE_TM_ZONE) || defined(WIN32)
- Tcl_MutexLock(&clockMutex);
-#endif
- result = TclpStrftime(buffer.string, (unsigned int) bufSize,
- Tcl_DStringValue(&uniBuffer), timeDataPtr, useGMT);
-#if defined(HAVE_TM_ZONE) || defined(WIN32)
- Tcl_MutexUnlock(&clockMutex);
-#endif
- Tcl_DStringFree(&uniBuffer);
-
-#if !defined(HAVE_TM_ZONE) && !defined(WIN32)
- 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();
- }
- Tcl_MutexUnlock( &clockMutex );
-#endif
+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 (result == 0) {
+ if (jday >= changeover) {
/*
- * 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.
+ * Gregorian calendar.
*/
- Tcl_AppendStringsToObj(Tcl_GetObjResult(interp),
- "bad format string \"", format, "\"", (char *) NULL);
+
+ 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 */
+
+ /*
+ * Find January 4 in the ISO8601 year, which will always be in week 1.
+ */
+
+ TclDateFields firstWeek;
+ 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; int ym1;
+ int month; int mm1;
+ int q; int r;
+ int ym1o4; int ym1o100; int 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]
+ + (ONE_YEAR * 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;
+ }
+ 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 */
+{
/*
- * Convert the time to UTF from external encoding [Bug: 3345]
+ * Get a thread-local buffer to hold the returned time.
*/
- Tcl_DStringInit(&uniBuffer);
- Tcl_ExternalToUtfDString(NULL, buffer.string, -1, &uniBuffer);
- Tcl_SetStringObj(Tcl_GetObjResult(interp), uniBuffer.string, -1);
+ struct tm *tmPtr = (struct tm *)
+ Tcl_GetThreadData(&tmKey, (int) sizeof(struct tm));
+#ifdef HAVE_LOCALTIME_R
+ localtime_r(timePtr, tmPtr);
+#else
+ struct tm *sysTmPtr;
- Tcl_DStringFree(&uniBuffer);
- Tcl_DStringFree(&buffer);
+ Tcl_MutexLock(&clockMutex);
+ sysTmPtr = localtime(timePtr);
+ if (sysTmPtr == NULL) {
+ Tcl_MutexUnlock(&clockMutex);
+ return NULL;
+ } else {
+ memcpy((void *) tmPtr, (void *) 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 *clicksSwitches[] = {
+ "-milliseconds", "-microseconds", 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: {
+#ifndef TCL_WIDE_CLICKS
+ unsigned long clicks = TclpGetClicks();
+#else
+ Tcl_WideInt clicks = TclpGetWideClicks();
+#endif
+ Tcl_SetObjResult(interp, Tcl_NewWideIntObj((Tcl_WideInt) clicks));
+ break;
+ }
+ case CLICKS_MICROS:
+ Tcl_GetTime(&now);
+ Tcl_SetObjResult(interp, Tcl_NewWideIntObj(
+ ((Tcl_WideInt) now.sec * 1000000) + now.usec));
+ break;
+ }
+
+ 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 = (ClockClientData*) clientData;
+ Tcl_Obj** litPtr = dataPtr->literals;
+
+ /* Format, locale and timezone */
+
+ Tcl_Obj* results[3];
+#define formatObj results[0]
+#define localeObj results[1]
+#define timezoneObj results[2]
+ int gmtFlag = 0;
+
+ /* Command line options expected */
+
+ static const char* options[] = {
+ "-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);
+ }
+
+ /* Check options */
+
+ 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];
+ }
+
+ /* 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 = (ClockClientData*) clientData;
+ int i;
+
+ --(data->refCount);
+ if (data->refCount == 0) {
+ for (i = 0; i < LIT__END; ++i) {
+ Tcl_DecrRefCount(data->literals[i]);
+ }
+ ckfree((char*) (data->literals));
+ ckfree((char*) data);
+ }
+}
+
+/*
+ * Local Variables:
+ * mode: c
+ * c-basic-offset: 4
+ * fill-column: 78
+ * End:
+ */