diff options
Diffstat (limited to 'generic/tclClock.c')
-rw-r--r-- | generic/tclClock.c | 2239 |
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: + */ |