tclClock.c

Go to the documentation of this file.
00001 /*
00002  * tclClock.c --
00003  *
00004  *      Contains the time and date related commands. This code is derived from
00005  *      the time and date facilities of TclX, by Mark Diekhans and Karl
00006  *      Lehenbauer.
00007  *
00008  * Copyright 1991-1995 Karl Lehenbauer and Mark Diekhans.
00009  * Copyright (c) 1995 Sun Microsystems, Inc.
00010  * Copyright (c) 2004 by Kevin B. Kenny.  All rights reserved.
00011  *
00012  * See the file "license.terms" for information on usage and redistribution of
00013  * this file, and for a DISCLAIMER OF ALL WARRANTIES.
00014  *
00015  * RCS: @(#) $Id: tclClock.c,v 1.63 2007/12/13 15:23:15 dgp Exp $
00016  */
00017 
00018 #include "tclInt.h"
00019 
00020 /*
00021  * Windows has mktime. The configurators do not check.
00022  */
00023 
00024 #ifdef __WIN32__
00025 #define HAVE_MKTIME 1
00026 #endif
00027 
00028 /*
00029  * Constants
00030  */
00031 
00032 #define JULIAN_DAY_POSIX_EPOCH          2440588
00033 #define SECONDS_PER_DAY                 86400
00034 #define JULIAN_SEC_POSIX_EPOCH        (((Tcl_WideInt) JULIAN_DAY_POSIX_EPOCH) \
00035                                         * SECONDS_PER_DAY)
00036 #define FOUR_CENTURIES                  146097 /* days */
00037 #define JDAY_1_JAN_1_CE_JULIAN          1721424
00038 #define JDAY_1_JAN_1_CE_GREGORIAN       1721426
00039 #define ONE_CENTURY_GREGORIAN           36524  /* days */
00040 #define FOUR_YEARS                      1461   /* days */
00041 #define ONE_YEAR                        365    /* days */
00042 
00043 /*
00044  * Table of the days in each month, leap and common years
00045  */
00046 
00047 static const int hath[2][12] = {
00048     {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
00049     {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
00050 };
00051 static const int daysInPriorMonths[2][13] = {
00052     {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365},
00053     {0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366}
00054 };
00055 
00056 /*
00057  * Enumeration of the string literals used in [clock]
00058  */
00059 
00060 typedef enum ClockLiteral {
00061     LIT_BCE,            LIT_CE,
00062     LIT_DAYOFMONTH,     LIT_DAYOFWEEK,          LIT_DAYOFYEAR,
00063     LIT_ERA,            LIT_GREGORIAN,
00064     LIT_ISO8601WEEK,    LIT_ISO8601YEAR,
00065     LIT_JULIANDAY,      LIT_LOCALSECONDS,
00066     LIT_MONTH,
00067     LIT_SECONDS,        LIT_TZNAME,             LIT_TZOFFSET,
00068     LIT_YEAR,
00069     LIT__END
00070 } ClockLiteral;
00071 static const char *const literals[] = {
00072     "BCE",              "CE",
00073     "dayOfMonth",       "dayOfWeek",            "dayOfYear",
00074     "era",              "gregorian",
00075     "iso8601Week",      "iso8601Year",
00076     "julianDay",        "localSeconds",
00077     "month",
00078     "seconds",          "tzName",               "tzOffset",
00079     "year"
00080 };
00081 
00082 /*
00083  * Structure containing the client data for [clock]
00084  */
00085 
00086 typedef struct ClockClientData {
00087     int refCount;               /* Number of live references */
00088     Tcl_Obj** literals;         /* Pool of object literals */
00089 } ClockClientData;
00090 
00091 /*
00092  * Structure containing the fields used in [clock format] and [clock scan]
00093  */
00094 
00095 typedef struct TclDateFields {
00096     Tcl_WideInt seconds;        /* Time expressed in seconds from the Posix
00097                                  * epoch */
00098     Tcl_WideInt localSeconds;   /* Local time expressed in nominal seconds
00099                                  * from the Posix epoch */
00100     int tzOffset;               /* Time zone offset in seconds east of
00101                                  * Greenwich */
00102     Tcl_Obj* tzName;            /* Time zone name */
00103     int julianDay;              /* Julian Day Number in local time zone */
00104     enum {BCE=1, CE=0} era;     /* Era */
00105     int gregorian;              /* Flag == 1 if the date is Gregorian */
00106     int year;                   /* Year of the era */
00107     int dayOfYear;              /* Day of the year (1 January == 1) */
00108     int month;                  /* Month number */
00109     int dayOfMonth;             /* Day of the month */
00110     int iso8601Year;            /* ISO8601 week-based year */
00111     int iso8601Week;            /* ISO8601 week number */
00112     int dayOfWeek;              /* Day of the week */
00113 } TclDateFields;
00114 static const char* eras[] = { "CE", "BCE", NULL };
00115 
00116 /*
00117  * Thread specific data block holding a 'struct tm' for the 'gmtime' and
00118  * 'localtime' library calls.
00119  */
00120 
00121 static Tcl_ThreadDataKey tmKey;
00122 
00123 /*
00124  * Mutex protecting 'gmtime', 'localtime' and 'mktime' calls and the statics
00125  * in the date parsing code.
00126  */
00127 
00128 TCL_DECLARE_MUTEX(clockMutex)
00129 
00130 /*
00131  * Function prototypes for local procedures in this file:
00132  */
00133 
00134 static int              ConvertUTCToLocal(Tcl_Interp*,
00135                             TclDateFields*, Tcl_Obj*, int);
00136 static int              ConvertUTCToLocalUsingTable(Tcl_Interp*,
00137                             TclDateFields*, int, Tcl_Obj *const[]);
00138 static int              ConvertUTCToLocalUsingC(Tcl_Interp*,
00139                             TclDateFields*, int);
00140 static int              ConvertLocalToUTC(Tcl_Interp*,
00141                             TclDateFields*, Tcl_Obj*, int);
00142 static int              ConvertLocalToUTCUsingTable(Tcl_Interp*,
00143                             TclDateFields*, int, Tcl_Obj *const[]);
00144 static int              ConvertLocalToUTCUsingC(Tcl_Interp*,
00145                             TclDateFields*, int);
00146 static Tcl_Obj*         LookupLastTransition(Tcl_Interp*, Tcl_WideInt,
00147                             int, Tcl_Obj *const *);
00148 static void             GetYearWeekDay(TclDateFields*, int);
00149 static void             GetGregorianEraYearDay(TclDateFields*, int);
00150 static void             GetMonthDay(TclDateFields*);
00151 static void             GetJulianDayFromEraYearWeekDay(TclDateFields*, int);
00152 static void             GetJulianDayFromEraYearMonthDay(TclDateFields*, int);
00153 static int              IsGregorianLeapYear(TclDateFields*);
00154 static int              WeekdayOnOrBefore(int, int);
00155 static int              ClockClicksObjCmd(
00156                             ClientData clientData, Tcl_Interp *interp,
00157                             int objc, Tcl_Obj *const objv[]);
00158 static int              ClockConvertlocaltoutcObjCmd(
00159                             ClientData clientData, Tcl_Interp *interp,
00160                             int objc, Tcl_Obj *const objv[]);
00161 static int              ClockGetdatefieldsObjCmd(
00162                             ClientData clientData, Tcl_Interp *interp,
00163                             int objc, Tcl_Obj *const objv[]);
00164 static int              ClockGetjuliandayfromerayearmonthdayObjCmd(
00165                             ClientData clientData, Tcl_Interp *interp,
00166                             int objc, Tcl_Obj *const objv[]);
00167 static int              ClockGetjuliandayfromerayearweekdayObjCmd(
00168                             ClientData clientData, Tcl_Interp *interp,
00169                             int objc, Tcl_Obj *const objv[]);
00170 static int              ClockGetenvObjCmd(
00171                             ClientData clientData, Tcl_Interp *interp,
00172                             int objc, Tcl_Obj *const objv[]);
00173 static int              ClockMicrosecondsObjCmd(
00174                             ClientData clientData, Tcl_Interp *interp,
00175                             int objc, Tcl_Obj *const objv[]);
00176 static int              ClockMillisecondsObjCmd(
00177                             ClientData clientData, Tcl_Interp *interp,
00178                             int objc, Tcl_Obj *const objv[]);
00179 static int              ClockSecondsObjCmd(
00180                             ClientData clientData, Tcl_Interp *interp,
00181                             int objc, Tcl_Obj *const objv[]);
00182 static struct tm *      ThreadSafeLocalTime(const time_t *);
00183 static void             TzsetIfNecessary(void);
00184 static void             ClockDeleteCmdProc(ClientData);
00185 
00186 /*
00187  * Structure containing description of "native" clock commands to create.
00188  */
00189 
00190 struct ClockCommand {
00191     const char *name;           /* The tail of the command name. The full name
00192                                  * is "::tcl::clock::<name>". When NULL marks
00193                                  * the end of the table. */
00194     Tcl_ObjCmdProc *objCmdProc; /* Function that implements the command. This
00195                                  * will always have the ClockClientData sent
00196                                  * to it, but may well ignore this data. */
00197 };
00198 
00199 static const struct ClockCommand clockCommands[] = {
00200     { "clicks",                 ClockClicksObjCmd },
00201     { "getenv",                 ClockGetenvObjCmd },
00202     { "microseconds",           ClockMicrosecondsObjCmd },
00203     { "milliseconds",           ClockMillisecondsObjCmd },
00204     { "seconds",                ClockSecondsObjCmd },
00205     { "Oldscan",                TclClockOldscanObjCmd },
00206     { "ConvertLocalToUTC",      ClockConvertlocaltoutcObjCmd },
00207     { "GetDateFields",          ClockGetdatefieldsObjCmd },
00208     { "GetJulianDayFromEraYearMonthDay",
00209                 ClockGetjuliandayfromerayearmonthdayObjCmd },
00210     { "GetJulianDayFromEraYearWeekDay",
00211                 ClockGetjuliandayfromerayearweekdayObjCmd },
00212     { NULL, NULL }
00213 };
00214 
00215 /*
00216  *----------------------------------------------------------------------
00217  *
00218  * TclClockInit --
00219  *
00220  *      Registers the 'clock' subcommands with the Tcl interpreter and
00221  *      initializes its client data (which consists mostly of constant
00222  *      Tcl_Obj's that it is too much trouble to keep recreating).
00223  *
00224  * Results:
00225  *      None.
00226  *
00227  * Side effects:
00228  *      Installs the commands and creates the client data
00229  *
00230  *----------------------------------------------------------------------
00231  */
00232 
00233 void
00234 TclClockInit(
00235     Tcl_Interp *interp)         /* Tcl interpreter */
00236 {
00237     const struct ClockCommand *clockCmdPtr;
00238     char cmdName[50];           /* Buffer large enough to hold the string
00239                                  *::tcl::clock::GetJulianDayFromEraYearMonthDay
00240                                  * plus a terminating NULL. */
00241     ClockClientData *data;
00242     int i;
00243 
00244     /*
00245      * Create the client data, which is a refcounted literal pool.
00246      */
00247 
00248     data = (ClockClientData *) ckalloc(sizeof(ClockClientData));
00249     data->refCount = 0;
00250     data->literals = (Tcl_Obj**) ckalloc(LIT__END * sizeof(Tcl_Obj*));
00251     for (i = 0; i < LIT__END; ++i) {
00252         data->literals[i] = Tcl_NewStringObj(literals[i], -1);
00253         Tcl_IncrRefCount(data->literals[i]);
00254     }
00255 
00256     /*
00257      * Install the commands.
00258      */
00259 
00260     strcpy(cmdName, "::tcl::clock::");
00261 #define TCL_CLOCK_PREFIX_LEN 14 /* == strlen("::tcl::clock::") */
00262     for (clockCmdPtr=clockCommands ; clockCmdPtr->name!=NULL ; clockCmdPtr++) {
00263         strcpy(cmdName + TCL_CLOCK_PREFIX_LEN, clockCmdPtr->name);
00264         data->refCount++;
00265         Tcl_CreateObjCommand(interp, cmdName, clockCmdPtr->objCmdProc, data,
00266                 ClockDeleteCmdProc);
00267     }
00268 }
00269 
00270 /*
00271  *----------------------------------------------------------------------
00272  *
00273  * ClockConvertlocaltoutcObjCmd --
00274  *
00275  *      Tcl command that converts a UTC time to a local time by whatever means
00276  *      is available.
00277  *
00278  * Usage:
00279  *      ::tcl::clock::ConvertUTCToLocal dictionary tzdata changeover
00280  *
00281  * Parameters:
00282  *      dict - Dictionary containing a 'localSeconds' entry.
00283  *      tzdata - Time zone data
00284  *      changeover - Julian Day of the adoption of the Gregorian calendar.
00285  *
00286  * Results:
00287  *      Returns a standard Tcl result.
00288  *
00289  * Side effects:
00290  *      On success, sets the interpreter result to the given dictionary
00291  *      augmented with a 'seconds' field giving the UTC time. On failure,
00292  *      leaves an error message in the interpreter result.
00293  *
00294  *----------------------------------------------------------------------
00295  */
00296 
00297 static int
00298 ClockConvertlocaltoutcObjCmd(
00299     ClientData clientData,      /* Client data  */
00300     Tcl_Interp* interp,         /* Tcl interpreter */
00301     int objc,                   /* Parameter count */
00302     Tcl_Obj *const *objv)       /* Parameter vector */
00303 {
00304     ClockClientData* data = (ClockClientData*) clientData;
00305     Tcl_Obj* const * literals = data->literals;
00306     Tcl_Obj* secondsObj;
00307     Tcl_Obj* dict;
00308     int changeover;
00309     TclDateFields fields;
00310     int created = 0;
00311     int status;
00312 
00313     /*
00314      * Check params and convert time.
00315      */
00316 
00317     if (objc != 4) {
00318         Tcl_WrongNumArgs(interp, 1, objv, "dict tzdata changeover");
00319         return TCL_ERROR;
00320     }
00321     dict = objv[1];
00322     if ((Tcl_DictObjGet(interp, dict, literals[LIT_LOCALSECONDS],
00323                 &secondsObj) != TCL_OK)
00324             || (Tcl_GetWideIntFromObj(interp, secondsObj,
00325                 &(fields.localSeconds)) != TCL_OK)
00326             || (TclGetIntFromObj(interp, objv[3], &changeover) != TCL_OK)
00327             || ConvertLocalToUTC(interp, &fields, objv[2], changeover)) {
00328         return TCL_ERROR;
00329     }
00330 
00331     /*
00332      * Copy-on-write; set the 'seconds' field in the dictionary and place the
00333      * modified dictionary in the interpreter result.
00334      */
00335 
00336     if (Tcl_IsShared(dict)) {
00337         dict = Tcl_DuplicateObj(dict);
00338         created = 1;
00339         Tcl_IncrRefCount(dict);
00340     }
00341     status = Tcl_DictObjPut(interp, dict, literals[LIT_SECONDS],
00342             Tcl_NewWideIntObj(fields.seconds));
00343     if (status == TCL_OK) {
00344         Tcl_SetObjResult(interp, dict);
00345     }
00346     if (created) {
00347         Tcl_DecrRefCount(dict);
00348     }
00349     return status;
00350 }
00351 
00352 /*
00353  *----------------------------------------------------------------------
00354  *
00355  * ClockGetdatefieldsObjCmd --
00356  *
00357  *      Tcl command that determines the values that [clock format] will use in
00358  *      formatting a date, and populates a dictionary with them.
00359  *
00360  * Usage:
00361  *      ::tcl::clock::GetDateFields seconds tzdata changeover
00362  *
00363  * Parameters:
00364  *      seconds - Time expressed in seconds from the Posix epoch.
00365  *      tzdata - Time zone data of the time zone in which time is to
00366  *                 be expressed.
00367  *      changeover - Julian Day Number at which the current locale adopted
00368  *                   the Gregorian calendar
00369  *
00370  * Results:
00371  *      Returns a dictonary populated with the fields:
00372  *              seconds - Seconds from the Posix epoch
00373  *              localSeconds - Nominal seconds from the Posix epoch in
00374  *                             the local time zone.
00375  *              tzOffset - Time zone offset in seconds east of Greenwich
00376  *              tzName - Time zone name
00377  *              julianDay - Julian Day Number in the local time zone
00378  *
00379  *----------------------------------------------------------------------
00380  */
00381 
00382 int
00383 ClockGetdatefieldsObjCmd(
00384     ClientData clientData,      /* Opaque pointer to literal pool, etc. */
00385     Tcl_Interp* interp,         /* Tcl interpreter */
00386     int objc,                   /* Parameter count */
00387     Tcl_Obj *const *objv)       /* Parameter vector */
00388 {
00389     TclDateFields fields;
00390     Tcl_Obj* dict;
00391     ClockClientData* data = (ClockClientData*) clientData;
00392     Tcl_Obj* const * literals = data->literals;
00393     int changeover;
00394 
00395     /*
00396      * Check params.
00397      */
00398 
00399     if (objc != 4) {
00400         Tcl_WrongNumArgs(interp, 1, objv, "seconds tzdata changeover");
00401         return TCL_ERROR;
00402     }
00403     if (Tcl_GetWideIntFromObj(interp, objv[1], &(fields.seconds)) != TCL_OK
00404             || TclGetIntFromObj(interp, objv[3], &changeover) != TCL_OK) {
00405         return TCL_ERROR;
00406     }
00407 
00408     /*
00409      * Convert UTC time to local.
00410      */
00411 
00412     if (ConvertUTCToLocal(interp, &fields, objv[2], changeover) != TCL_OK) {
00413         return TCL_ERROR;
00414     }
00415 
00416     /*
00417      * Extract Julian day.
00418      */
00419 
00420     fields.julianDay = (int) ((fields.localSeconds + JULIAN_SEC_POSIX_EPOCH)
00421             / SECONDS_PER_DAY);
00422 
00423     /*
00424      * Convert to Julian or Gregorian calendar.
00425      */
00426 
00427     GetGregorianEraYearDay(&fields, changeover);
00428     GetMonthDay(&fields);
00429     GetYearWeekDay(&fields, changeover);
00430 
00431     dict = Tcl_NewDictObj();
00432     Tcl_DictObjPut(NULL, dict, literals[LIT_LOCALSECONDS],
00433             Tcl_NewWideIntObj(fields.localSeconds));
00434     Tcl_DictObjPut(NULL, dict, literals[LIT_SECONDS],
00435             Tcl_NewWideIntObj(fields.seconds));
00436     Tcl_DictObjPut(NULL, dict, literals[LIT_TZNAME], fields.tzName);
00437     Tcl_DecrRefCount(fields.tzName);
00438     Tcl_DictObjPut(NULL, dict, literals[LIT_TZOFFSET],
00439             Tcl_NewIntObj(fields.tzOffset));
00440     Tcl_DictObjPut(NULL, dict, literals[LIT_JULIANDAY],
00441             Tcl_NewIntObj(fields.julianDay));
00442     Tcl_DictObjPut(NULL, dict, literals[LIT_GREGORIAN],
00443             Tcl_NewIntObj(fields.gregorian));
00444     Tcl_DictObjPut(NULL, dict, literals[LIT_ERA],
00445             literals[fields.era ? LIT_BCE : LIT_CE]);
00446     Tcl_DictObjPut(NULL, dict, literals[LIT_YEAR],
00447             Tcl_NewIntObj(fields.year));
00448     Tcl_DictObjPut(NULL, dict, literals[LIT_DAYOFYEAR],
00449             Tcl_NewIntObj(fields.dayOfYear));
00450     Tcl_DictObjPut(NULL, dict, literals[LIT_MONTH],
00451             Tcl_NewIntObj(fields.month));
00452     Tcl_DictObjPut(NULL, dict, literals[LIT_DAYOFMONTH],
00453             Tcl_NewIntObj(fields.dayOfMonth));
00454     Tcl_DictObjPut(NULL, dict, literals[LIT_ISO8601YEAR],
00455             Tcl_NewIntObj(fields.iso8601Year));
00456     Tcl_DictObjPut(NULL, dict, literals[LIT_ISO8601WEEK],
00457             Tcl_NewIntObj(fields.iso8601Week));
00458     Tcl_DictObjPut(NULL, dict, literals[LIT_DAYOFWEEK],
00459             Tcl_NewIntObj(fields.dayOfWeek));
00460     Tcl_SetObjResult(interp, dict);
00461 
00462     return TCL_OK;
00463 }
00464 
00465 /*
00466  *----------------------------------------------------------------------
00467  *
00468  * ClockGetjuliandayfromerayearmonthdayObjCmd --
00469  *
00470  *      Tcl command that converts a time from era-year-month-day to a Julian
00471  *      Day Number.
00472  *
00473  * Parameters:
00474  *      dict - Dictionary that contains 'era', 'year', 'month' and
00475  *             'dayOfMonth' keys.
00476  *      changeover - Julian Day of changeover to the Gregorian calendar
00477  *
00478  * Results:
00479  *      Result is either TCL_OK, with the interpreter result being the
00480  *      dictionary augmented with a 'julianDay' key, or TCL_ERROR,
00481  *      with the result being an error message.
00482  *
00483  *----------------------------------------------------------------------
00484  */
00485 
00486 static int
00487 ClockGetjuliandayfromerayearmonthdayObjCmd (
00488     ClientData clientData,      /* Opaque pointer to literal pool, etc. */
00489     Tcl_Interp* interp,         /* Tcl interpreter */
00490     int objc,                   /* Parameter count */
00491     Tcl_Obj *const *objv)       /* Parameter vector */
00492 {
00493     TclDateFields fields;
00494     Tcl_Obj* dict;
00495     ClockClientData* data = (ClockClientData*) clientData;
00496     Tcl_Obj* const * literals = data->literals;
00497     Tcl_Obj* fieldPtr;
00498     int changeover;
00499     int copied = 0;
00500     int status;
00501     int era = 0;
00502 
00503     /*
00504      * Check params.
00505      */
00506 
00507     if (objc != 3) {
00508         Tcl_WrongNumArgs(interp, 1, objv, "dict changeover");
00509         return TCL_ERROR;
00510     }
00511     dict = objv[1];
00512     if (Tcl_DictObjGet(interp, dict, literals[LIT_ERA], &fieldPtr) != TCL_OK
00513             || Tcl_GetIndexFromObj(interp, fieldPtr, eras, "era", TCL_EXACT,
00514                 &era) != TCL_OK
00515             || Tcl_DictObjGet(interp, dict, literals[LIT_YEAR],
00516                 &fieldPtr) != TCL_OK
00517             || TclGetIntFromObj(interp, fieldPtr, &(fields.year)) != TCL_OK
00518             || Tcl_DictObjGet(interp, dict, literals[LIT_MONTH],
00519                 &fieldPtr) != TCL_OK
00520             || TclGetIntFromObj(interp, fieldPtr, &(fields.month)) != TCL_OK
00521             || Tcl_DictObjGet(interp, dict, literals[LIT_DAYOFMONTH],
00522                 &fieldPtr) != TCL_OK
00523             || TclGetIntFromObj(interp, fieldPtr,
00524                 &(fields.dayOfMonth)) != TCL_OK
00525             || TclGetIntFromObj(interp, objv[2], &changeover) != TCL_OK) {
00526         return TCL_ERROR;
00527     }
00528     fields.era = era;
00529 
00530     /*
00531      * Get Julian day.
00532      */
00533 
00534     GetJulianDayFromEraYearMonthDay(&fields, changeover);
00535 
00536     /*
00537      * Store Julian day in the dictionary - copy on write.
00538      */
00539 
00540     if (Tcl_IsShared(dict)) {
00541         dict = Tcl_DuplicateObj(dict);
00542         Tcl_IncrRefCount(dict);
00543         copied = 1;
00544     }
00545     status = Tcl_DictObjPut(interp, dict, literals[LIT_JULIANDAY],
00546             Tcl_NewIntObj(fields.julianDay));
00547     if (status == TCL_OK) {
00548         Tcl_SetObjResult(interp, dict);
00549     }
00550     if (copied) {
00551         Tcl_DecrRefCount(dict);
00552     }
00553     return status;
00554 }
00555 
00556 /*
00557  *----------------------------------------------------------------------
00558  *
00559  * ClockGetjuliandayfromerayearweekdayObjCmd --
00560  *
00561  *      Tcl command that converts a time from the ISO calendar to a Julian Day
00562  *      Number.
00563  *
00564  * Parameters:
00565  *      dict - Dictionary that contains 'era', 'iso8601Year', 'iso8601Week'
00566  *             and 'dayOfWeek' keys.
00567  *      changeover - Julian Day of changeover to the Gregorian calendar
00568  *
00569  * Results:
00570  *      Result is either TCL_OK, with the interpreter result being the
00571  *      dictionary augmented with a 'julianDay' key, or TCL_ERROR, with the
00572  *      result being an error message.
00573  *
00574  *----------------------------------------------------------------------
00575  */
00576 
00577 static int
00578 ClockGetjuliandayfromerayearweekdayObjCmd (
00579     ClientData clientData,      /* Opaque pointer to literal pool, etc. */
00580     Tcl_Interp* interp,         /* Tcl interpreter */
00581     int objc,                   /* Parameter count */
00582     Tcl_Obj *const *objv)       /* Parameter vector */
00583 {
00584     TclDateFields fields;
00585     Tcl_Obj* dict;
00586     ClockClientData* data = (ClockClientData*) clientData;
00587     Tcl_Obj* const * literals = data->literals;
00588     Tcl_Obj* fieldPtr;
00589     int changeover;
00590     int copied = 0;
00591     int status;
00592     int era = 0;
00593 
00594     /*
00595      * Check params.
00596      */
00597 
00598     if (objc != 3) {
00599         Tcl_WrongNumArgs(interp, 1, objv, "dict changeover");
00600         return TCL_ERROR;
00601     }
00602     dict = objv[1];
00603     if (Tcl_DictObjGet(interp, dict, literals[LIT_ERA], &fieldPtr) != TCL_OK
00604             || Tcl_GetIndexFromObj(interp, fieldPtr, eras, "era", TCL_EXACT,
00605                 &era) != TCL_OK
00606             || Tcl_DictObjGet(interp, dict, literals[LIT_ISO8601YEAR],
00607                 &fieldPtr) != TCL_OK
00608             || TclGetIntFromObj(interp, fieldPtr,
00609                 &(fields.iso8601Year)) != TCL_OK
00610             || Tcl_DictObjGet(interp, dict, literals[LIT_ISO8601WEEK],
00611                 &fieldPtr) != TCL_OK
00612             || TclGetIntFromObj(interp, fieldPtr,
00613                 &(fields.iso8601Week)) != TCL_OK
00614             || Tcl_DictObjGet(interp, dict, literals[LIT_DAYOFWEEK],
00615                 &fieldPtr) != TCL_OK
00616             || TclGetIntFromObj(interp, fieldPtr,
00617                 &(fields.dayOfWeek)) != TCL_OK
00618             || TclGetIntFromObj(interp, objv[2], &changeover) != TCL_OK) {
00619         return TCL_ERROR;
00620     }
00621     fields.era = era;
00622 
00623     /*
00624      * Get Julian day.
00625      */
00626 
00627     GetJulianDayFromEraYearWeekDay(&fields, changeover);
00628 
00629     /*
00630      * Store Julian day in the dictionary - copy on write.
00631      */
00632 
00633     if (Tcl_IsShared(dict)) {
00634         dict = Tcl_DuplicateObj(dict);
00635         Tcl_IncrRefCount(dict);
00636         copied = 1;
00637     }
00638     status = Tcl_DictObjPut(interp, dict, literals[LIT_JULIANDAY],
00639             Tcl_NewIntObj(fields.julianDay));
00640     if (status == TCL_OK) {
00641         Tcl_SetObjResult(interp, dict);
00642     }
00643     if (copied) {
00644         Tcl_DecrRefCount(dict);
00645     }
00646     return status;
00647 }
00648 
00649 /*
00650  *----------------------------------------------------------------------
00651  *
00652  * ConvertLocalToUTC --
00653  *
00654  *      Converts a time (in a TclDateFields structure) from the local wall
00655  *      clock to UTC.
00656  *
00657  * Results:
00658  *      Returns a standard Tcl result.
00659  *
00660  * Side effects:
00661  *      Populates the 'seconds' field if successful; stores an error message
00662  *      in the interpreter result on failure.
00663  *
00664  *----------------------------------------------------------------------
00665  */
00666 
00667 static int
00668 ConvertLocalToUTC(
00669     Tcl_Interp* interp,         /* Tcl interpreter */
00670     TclDateFields* fields,      /* Fields of the time */
00671     Tcl_Obj* tzdata,            /* Time zone data */
00672     int changeover)             /* Julian Day of the Gregorian transition */
00673 {
00674     int rowc;                   /* Number of rows in tzdata */
00675     Tcl_Obj** rowv;             /* Pointers to the rows */
00676 
00677     /*
00678      * Unpack the tz data.
00679      */
00680 
00681     if (TclListObjGetElements(interp, tzdata, &rowc, &rowv) != TCL_OK) {
00682         return TCL_ERROR;
00683     }
00684 
00685     /*
00686      * Special case: If the time zone is :localtime, the tzdata will be empty.
00687      * Use 'mktime' to convert the time to local
00688      */
00689 
00690     if (rowc == 0) {
00691         return ConvertLocalToUTCUsingC(interp, fields, changeover);
00692     } else {
00693         return ConvertLocalToUTCUsingTable(interp, fields, rowc, rowv);
00694     }
00695 }
00696 
00697 /*
00698  *----------------------------------------------------------------------
00699  *
00700  * ConvertLocalToUTCUsingTable --
00701  *
00702  *      Converts a time (in a TclDateFields structure) from local time in a
00703  *      given time zone to UTC.
00704  *
00705  * Results:
00706  *      Returns a standard Tcl result.
00707  *
00708  * Side effects:
00709  *      Stores an error message in the interpreter if an error occurs; if
00710  *      successful, stores the 'seconds' field in 'fields.
00711  *
00712  *----------------------------------------------------------------------
00713  */
00714 
00715 static int
00716 ConvertLocalToUTCUsingTable(
00717     Tcl_Interp* interp,         /* Tcl interpreter */
00718     TclDateFields* fields,      /* Time to convert, with 'seconds' filled in */
00719     int rowc,                   /* Number of points at which time changes */
00720     Tcl_Obj *const rowv[])      /* Points at which time changes */
00721 {
00722     Tcl_Obj* row;
00723     int cellc;
00724     Tcl_Obj** cellv;
00725     int have[8];
00726     int nHave = 0;
00727     int i;
00728     int found;
00729 
00730     /*
00731      * Perform an initial lookup assuming that local == UTC, and locate the
00732      * last time conversion prior to that time. Get the offset from that row,
00733      * and look up again. Continue until we find an offset that we found
00734      * before. This definition, rather than "the same offset" ensures that we
00735      * don't enter an endless loop, as would otherwise happen when trying to
00736      * convert a non-existent time such as 02:30 during the US Spring Daylight
00737      * Saving Time transition.
00738      */
00739 
00740     found = 0;
00741     fields->tzOffset = 0;
00742     fields->seconds = fields->localSeconds;
00743     while (!found) {
00744         row = LookupLastTransition(interp, fields->seconds, rowc, rowv);
00745         if ((row == NULL)
00746                 || TclListObjGetElements(interp, row, &cellc,
00747                     &cellv) != TCL_OK
00748                 || TclGetIntFromObj(interp, cellv[1],
00749                     &(fields->tzOffset)) != TCL_OK) {
00750             return TCL_ERROR;
00751         }
00752         found = 0;
00753         for (i = 0; !found && i < nHave; ++i) {
00754             if (have[i] == fields->tzOffset) {
00755                 found = 1;
00756                 break;
00757             }
00758         }
00759         if (!found) {
00760             if (nHave == 8) {
00761                 Tcl_Panic("loop in ConvertLocalToUTCUsingTable");
00762             }
00763             have[nHave] = fields->tzOffset;
00764             ++nHave;
00765         }
00766         fields->seconds = fields->localSeconds - fields->tzOffset;
00767     }
00768     fields->tzOffset = have[i];
00769     fields->seconds = fields->localSeconds - fields->tzOffset;
00770     return TCL_OK;
00771 }
00772 
00773 /*
00774  *----------------------------------------------------------------------
00775  *
00776  * ConvertLocalToUTCUsingC --
00777  *
00778  *      Converts a time from local wall clock to UTC when the local time zone
00779  *      cannot be determined. Uses 'mktime' to do the job.
00780  *
00781  * Results:
00782  *      Returns a standard Tcl result.
00783  *
00784  * Side effects:
00785  *      Stores an error message in the interpreter if an error occurs; if
00786  *      successful, stores the 'seconds' field in 'fields.
00787  *
00788  *----------------------------------------------------------------------
00789  */
00790 
00791 static int
00792 ConvertLocalToUTCUsingC(
00793     Tcl_Interp* interp,         /* Tcl interpreter */
00794     TclDateFields* fields,      /* Time to convert, with 'seconds' filled in */
00795     int changeover)             /* Julian Day of the Gregorian transition */
00796 {
00797     struct tm timeVal;
00798     int localErrno;
00799     int secondOfDay;
00800     Tcl_WideInt jsec;
00801 
00802     /*
00803      * Convert the given time to a date.
00804      */
00805 
00806     jsec = fields->localSeconds + JULIAN_SEC_POSIX_EPOCH;
00807     fields->julianDay = (int) (jsec / SECONDS_PER_DAY);
00808     secondOfDay = (int)(jsec % SECONDS_PER_DAY);
00809     if (secondOfDay < 0) {
00810         secondOfDay += SECONDS_PER_DAY;
00811         --fields->julianDay;
00812     }
00813     GetGregorianEraYearDay(fields, changeover);
00814     GetMonthDay(fields);
00815 
00816     /*
00817      * Convert the date/time to a 'struct tm'.
00818      */
00819 
00820     timeVal.tm_year = fields->year - 1900;
00821     timeVal.tm_mon = fields->month - 1;
00822     timeVal.tm_mday = fields->dayOfMonth;
00823     timeVal.tm_hour = (secondOfDay / 3600) % 24;
00824     timeVal.tm_min = (secondOfDay / 60) % 60;
00825     timeVal.tm_sec = secondOfDay % 60;
00826     timeVal.tm_isdst = -1;
00827     timeVal.tm_wday = -1;
00828     timeVal.tm_yday = -1;
00829 
00830     /*
00831      * Get local time. It is rumored that mktime is not thread safe on some
00832      * platforms, so seize a mutex before attempting this.
00833      */
00834 
00835     TzsetIfNecessary();
00836     Tcl_MutexLock(&clockMutex);
00837     errno = 0;
00838     fields->seconds = (Tcl_WideInt) mktime(&timeVal);
00839     localErrno = errno;
00840     Tcl_MutexUnlock(&clockMutex);
00841 
00842     /*
00843      * If conversion fails, report an error.
00844      */
00845 
00846     if (localErrno != 0
00847             || (fields->seconds == -1 && timeVal.tm_yday == -1)) {
00848         Tcl_SetResult(interp, "time value too large/small to represent",
00849                 TCL_STATIC);
00850         return TCL_ERROR;
00851     }
00852     return TCL_OK;
00853 }
00854 
00855 /*
00856  *----------------------------------------------------------------------
00857  *
00858  * ConvertUTCToLocal --
00859  *
00860  *      Converts a time (in a TclDateFields structure) from UTC to local time.
00861  *
00862  * Results:
00863  *      Returns a standard Tcl result.
00864  *
00865  * Side effects:
00866  *      Populates the 'tzName' and 'tzOffset' fields.
00867  *
00868  *----------------------------------------------------------------------
00869  */
00870 
00871 static int
00872 ConvertUTCToLocal(
00873     Tcl_Interp* interp,         /* Tcl interpreter */
00874     TclDateFields* fields,      /* Fields of the time */
00875     Tcl_Obj* tzdata,            /* Time zone data */
00876     int changeover)             /* Julian Day of the Gregorian transition */
00877 {
00878     int rowc;                   /* Number of rows in tzdata */
00879     Tcl_Obj** rowv;             /* Pointers to the rows */
00880 
00881     /*
00882      * Unpack the tz data.
00883      */
00884 
00885     if (TclListObjGetElements(interp, tzdata, &rowc, &rowv) != TCL_OK) {
00886         return TCL_ERROR;
00887     }
00888 
00889     /*
00890      * Special case: If the time zone is :localtime, the tzdata will be empty.
00891      * Use 'localtime' to convert the time to local
00892      */
00893 
00894     if (rowc == 0) {
00895         return ConvertUTCToLocalUsingC(interp, fields, changeover);
00896     } else {
00897         return ConvertUTCToLocalUsingTable(interp, fields, rowc, rowv);
00898     }
00899 }
00900 
00901 /*
00902  *----------------------------------------------------------------------
00903  *
00904  * ConvertUTCToLocalUsingTable --
00905  *
00906  *      Converts UTC to local time, given a table of transition points
00907  *
00908  * Results:
00909  *      Returns a standard Tcl result
00910  *
00911  * Side effects:
00912  *      On success, fills fields->tzName, fields->tzOffset and
00913  *      fields->localSeconds. On failure, places an error message in the
00914  *      interpreter result.
00915  *
00916  *----------------------------------------------------------------------
00917  */
00918 
00919 static int
00920 ConvertUTCToLocalUsingTable(
00921     Tcl_Interp* interp,         /* Tcl interpreter */
00922     TclDateFields* fields,      /* Fields of the date */
00923     int rowc,                   /* Number of rows in the conversion table
00924                                  * (>= 1) */
00925     Tcl_Obj *const rowv[])      /* Rows of the conversion table */
00926 {
00927     Tcl_Obj* row;               /* Row containing the current information */
00928     int cellc;                  /* Count of cells in the row (must be 4) */
00929     Tcl_Obj** cellv;            /* Pointers to the cells */
00930 
00931     /*
00932      * Look up the nearest transition time.
00933      */
00934 
00935     row = LookupLastTransition(interp, fields->seconds, rowc, rowv);
00936     if (row == NULL ||
00937             TclListObjGetElements(interp, row, &cellc, &cellv) != TCL_OK ||
00938             TclGetIntFromObj(interp,cellv[1],&(fields->tzOffset)) != TCL_OK) {
00939         return TCL_ERROR;
00940     }
00941 
00942     /*
00943      * Convert the time.
00944      */
00945 
00946     fields->tzName = cellv[3];
00947     Tcl_IncrRefCount(fields->tzName);
00948     fields->localSeconds = fields->seconds + fields->tzOffset;
00949     return TCL_OK;
00950 }
00951 
00952 /*
00953  *----------------------------------------------------------------------
00954  *
00955  * ConvertUTCToLocalUsingC --
00956  *
00957  *      Converts UTC to localtime in cases where the local time zone is not
00958  *      determinable, using the C 'localtime' function to do it.
00959  *
00960  * Results:
00961  *      Returns a standard Tcl result.
00962  *
00963  * Side effects:
00964  *      On success, fills fields->tzName, fields->tzOffset and
00965  *      fields->localSeconds. On failure, places an error message in the
00966  *      interpreter result.
00967  *
00968  *----------------------------------------------------------------------
00969  */
00970 
00971 static int
00972 ConvertUTCToLocalUsingC(
00973     Tcl_Interp* interp,         /* Tcl interpreter */
00974     TclDateFields* fields,      /* Time to convert, with 'seconds' filled in */
00975     int changeover)             /* Julian Day of the Gregorian transition */
00976 {
00977     time_t tock;
00978     struct tm* timeVal;         /* Time after conversion */
00979     int diff;                   /* Time zone diff local-Greenwich */
00980     char buffer[8];             /* Buffer for time zone name */
00981 
00982     /*
00983      * Use 'localtime' to determine local year, month, day, time of day.
00984      */
00985 
00986     tock = (time_t) fields->seconds;
00987     if ((Tcl_WideInt) tock != fields->seconds) {
00988         Tcl_AppendResult(interp,
00989                 "number too large to represent as a Posix time", NULL);
00990         Tcl_SetErrorCode(interp, "CLOCK", "argTooLarge", NULL);
00991         return TCL_ERROR;
00992     }
00993     TzsetIfNecessary();
00994     timeVal = ThreadSafeLocalTime(&tock);
00995     if (timeVal == NULL) {
00996         Tcl_AppendResult(interp,
00997                 "localtime failed (clock value may be too "
00998                 "large/small to represent)", NULL);
00999         Tcl_SetErrorCode(interp, "CLOCK", "localtimeFailed", NULL);
01000         return TCL_ERROR;
01001     }
01002 
01003     /*
01004      * Fill in the date in 'fields' and use it to derive Julian Day.
01005      */
01006 
01007     fields->era = CE;
01008     fields->year = timeVal->tm_year + 1900;
01009     fields->month = timeVal->tm_mon + 1;
01010     fields->dayOfMonth = timeVal->tm_mday;
01011     GetJulianDayFromEraYearMonthDay(fields, changeover);
01012 
01013     /*
01014      * Convert that value to seconds.
01015      */
01016 
01017     fields->localSeconds = (((fields->julianDay * (Tcl_WideInt) 24
01018             + timeVal->tm_hour) * 60 + timeVal->tm_min) * 60
01019             + timeVal->tm_sec) - JULIAN_SEC_POSIX_EPOCH;
01020 
01021     /*
01022      * Determine a time zone offset and name; just use +hhmm for the name.
01023      */
01024 
01025     diff = (int) (fields->localSeconds - fields->seconds);
01026     fields->tzOffset = diff;
01027     if (diff < 0) {
01028         *buffer = '-';
01029         diff = -diff;
01030     } else {
01031         *buffer = '+';
01032     }
01033     sprintf(buffer+1, "%02d", diff / 3600);
01034     diff %= 3600;
01035     sprintf(buffer+3, "%02d", diff / 60);
01036     diff %= 60;
01037     if (diff > 0) {
01038         sprintf(buffer+5, "%02d", diff);
01039     }
01040     fields->tzName = Tcl_NewStringObj(buffer, -1);
01041     Tcl_IncrRefCount(fields->tzName);
01042     return TCL_OK;
01043 }
01044 
01045 /*
01046  *----------------------------------------------------------------------
01047  *
01048  * LookupLastTransition --
01049  *
01050  *      Given a UTC time and a tzdata array, looks up the last transition on
01051  *      or before the given time.
01052  *
01053  * Results:
01054  *      Returns a pointer to the row, or NULL if an error occurs.
01055  *
01056  *----------------------------------------------------------------------
01057  */
01058 
01059 static Tcl_Obj*
01060 LookupLastTransition(
01061     Tcl_Interp* interp,         /* Interpreter for error messages */
01062     Tcl_WideInt tick,           /* Time from the epoch */
01063     int rowc,                   /* Number of rows of tzdata */
01064     Tcl_Obj *const *rowv)       /* Rows in tzdata */
01065 {
01066     int l;
01067     int u;
01068     Tcl_Obj* compObj;
01069     Tcl_WideInt compVal;
01070 
01071     /*
01072      * Examine the first row to make sure we're in bounds.
01073      */
01074 
01075     if (Tcl_ListObjIndex(interp, rowv[0], 0, &compObj) != TCL_OK
01076             || Tcl_GetWideIntFromObj(interp, compObj, &compVal) != TCL_OK) {
01077         return NULL;
01078     }
01079 
01080     /*
01081      * Bizarre case - first row doesn't begin at MIN_WIDE_INT. Return it
01082      * anyway.
01083      */
01084 
01085     if (tick < compVal) {
01086         return rowv[0];
01087     }
01088 
01089     /*
01090      * Binary-search to find the transition.
01091      */
01092 
01093     l = 0;
01094     u = rowc-1;
01095     while (l < u) {
01096         int m = (l + u + 1) / 2;
01097 
01098         if (Tcl_ListObjIndex(interp, rowv[m], 0, &compObj) != TCL_OK ||
01099                 Tcl_GetWideIntFromObj(interp, compObj, &compVal) != TCL_OK) {
01100             return NULL;
01101         }
01102         if (tick >= compVal) {
01103             l = m;
01104         } else {
01105             u = m-1;
01106         }
01107     }
01108     return rowv[l];
01109 }
01110 
01111 /*
01112  *----------------------------------------------------------------------
01113  *
01114  * GetYearWeekDay --
01115  *
01116  *      Given a date with Julian Calendar Day, compute the year, week, and day
01117  *      in the ISO8601 calendar.
01118  *
01119  * Results:
01120  *      None.
01121  *
01122  * Side effects:
01123  *      Stores 'iso8601Year', 'iso8601Week' and 'dayOfWeek' in the date
01124  *      fields.
01125  *
01126  *----------------------------------------------------------------------
01127  */
01128 
01129 static void
01130 GetYearWeekDay(
01131     TclDateFields* fields,      /* Date to convert, must have 'julianDay' */
01132     int changeover)             /* Julian Day Number of the Gregorian
01133                                  * transition */
01134 {
01135     TclDateFields temp;
01136     int dayOfFiscalYear;
01137 
01138     /*
01139      * Find the given date, minus three days, plus one year. That date's
01140      * iso8601 year is an upper bound on the ISO8601 year of the given date.
01141      */
01142 
01143     temp.julianDay = fields->julianDay - 3;
01144     GetGregorianEraYearDay(&temp, changeover);
01145     if (temp.era == BCE) {
01146         temp.iso8601Year = temp.year - 1;
01147     } else {
01148         temp.iso8601Year = temp.year + 1;
01149     }
01150     temp.iso8601Week = 1;
01151     temp.dayOfWeek = 1;
01152     GetJulianDayFromEraYearWeekDay(&temp, changeover);
01153 
01154     /*
01155      * temp.julianDay is now the start of an ISO8601 year, either the one
01156      * corresponding to the given date, or the one after. If we guessed high,
01157      * move one year earlier
01158      */
01159 
01160     if (fields->julianDay < temp.julianDay) {
01161         if (temp.era == BCE) {
01162             temp.iso8601Year += 1;
01163         } else {
01164             temp.iso8601Year -= 1;
01165         }
01166         GetJulianDayFromEraYearWeekDay(&temp, changeover);
01167     }
01168 
01169     fields->iso8601Year = temp.iso8601Year;
01170     dayOfFiscalYear = fields->julianDay - temp.julianDay;
01171     fields->iso8601Week = (dayOfFiscalYear / 7) + 1;
01172     fields->dayOfWeek = (dayOfFiscalYear + 1) % 7;
01173     if (fields->dayOfWeek < 1) {
01174         fields->dayOfWeek += 7;
01175     }
01176 }
01177 
01178 /*
01179  *----------------------------------------------------------------------
01180  *
01181  * GetGregorianEraYearDay --
01182  *
01183  *      Given a Julian Day Number, extracts the year and day of the year and
01184  *      puts them into TclDateFields, along with the era (BCE or CE) and a
01185  *      flag indicating whether the date is Gregorian or Julian.
01186  *
01187  * Results:
01188  *      None.
01189  *
01190  * Side effects:
01191  *      Stores 'era', 'gregorian', 'year', and 'dayOfYear'.
01192  *
01193  *----------------------------------------------------------------------
01194  */
01195 
01196 static void
01197 GetGregorianEraYearDay(
01198     TclDateFields* fields,      /* Date fields containing 'julianDay' */
01199     int changeover)             /* Gregorian transition date */
01200 {
01201     int jday = fields->julianDay;
01202     int day;
01203     int year;
01204     int n;
01205 
01206     if (jday >= changeover) {
01207         /*
01208          * Gregorian calendar.
01209          */
01210 
01211         fields->gregorian = 1;
01212         year = 1;
01213 
01214         /*
01215          * n = Number of 400-year cycles since 1 January, 1 CE in the
01216          * proleptic Gregorian calendar. day = remaining days.
01217          */
01218 
01219         day = jday - JDAY_1_JAN_1_CE_GREGORIAN;
01220         n = day / FOUR_CENTURIES;
01221         day %= FOUR_CENTURIES;
01222         if (day < 0) {
01223             day += FOUR_CENTURIES;
01224             --n;
01225         }
01226         year += 400 * n;
01227 
01228         /*
01229          * n = number of centuries since the start of (year);
01230          * day = remaining days
01231          */
01232 
01233         n = day / ONE_CENTURY_GREGORIAN;
01234         day %= ONE_CENTURY_GREGORIAN;
01235         if (n > 3) {
01236             /*
01237              * 31 December in the last year of a 400-year cycle.
01238              */
01239 
01240             n = 3;
01241             day += ONE_CENTURY_GREGORIAN;
01242         }
01243         year += 100 * n;
01244 
01245     } else {
01246         /*
01247          * Julian calendar.
01248          */
01249 
01250         fields->gregorian = 0;
01251         year = 1;
01252         day = jday - JDAY_1_JAN_1_CE_JULIAN;
01253 
01254     }
01255 
01256     /*
01257      * n = number of 4-year cycles; days = remaining days.
01258      */
01259 
01260     n = day / FOUR_YEARS;
01261     day %= FOUR_YEARS;
01262     if (day < 0) {
01263         day += FOUR_YEARS;
01264         --n;
01265     }
01266     year += 4 * n;
01267 
01268     /*
01269      * n = number of years; days = remaining days.
01270      */
01271 
01272     n = day / ONE_YEAR;
01273     day %= ONE_YEAR;
01274     if (n > 3) {
01275         /*
01276          * 31 December of a leap year.
01277          */
01278 
01279         n = 3;
01280         day += 365;
01281     }
01282     year += n;
01283 
01284     /*
01285      * store era/year/day back into fields.
01286      */
01287 
01288     if (year <= 0) {
01289         fields->era = BCE;
01290         fields->year = 1 - year;
01291     } else {
01292         fields->era = CE;
01293         fields->year = year;
01294     }
01295     fields->dayOfYear = day + 1;
01296 }
01297 
01298 /*
01299  *----------------------------------------------------------------------
01300  *
01301  * GetMonthDay --
01302  *
01303  *      Given a date as year and day-of-year, find month and day.
01304  *
01305  * Results:
01306  *      None.
01307  *
01308  * Side effects:
01309  *      Stores 'month' and 'dayOfMonth' in the 'fields' structure.
01310  *
01311  *----------------------------------------------------------------------
01312  */
01313 
01314 static void
01315 GetMonthDay(
01316     TclDateFields* fields)      /* Date to convert */
01317 {
01318     int day = fields->dayOfYear;
01319     int month;
01320     const int* h = hath[IsGregorianLeapYear(fields)];
01321 
01322     for (month = 0; month < 12 && day > h[month]; ++month) {
01323         day -= h[month];
01324     }
01325     fields->month = month+1;
01326     fields->dayOfMonth = day;
01327 }
01328 
01329 /*
01330  *----------------------------------------------------------------------
01331  *
01332  * GetJulianDayFromEraYearWeekDay --
01333  *
01334  *      Given a TclDateFields structure containing era, ISO8601 year, ISO8601
01335  *      week, and day of week, computes the Julian Day Number.
01336  *
01337  * Results:
01338  *      None.
01339  *
01340  * Side effects:
01341  *      Stores 'julianDay' in the fields.
01342  *
01343  *----------------------------------------------------------------------
01344  */
01345 
01346 static void
01347 GetJulianDayFromEraYearWeekDay(
01348     TclDateFields* fields,      /* Date to convert */
01349     int changeover)             /* Julian Day Number of the Gregorian
01350                                  * transition */
01351 {
01352     int firstMonday;            /* Julian day number of week 1, day 1 in the
01353                                  * given year */
01354 
01355     /*
01356      * Find January 4 in the ISO8601 year, which will always be in week 1.
01357      */
01358 
01359     TclDateFields firstWeek;
01360     firstWeek.era = fields->era;
01361     firstWeek.year = fields->iso8601Year;
01362     firstWeek.month = 1;
01363     firstWeek.dayOfMonth = 4;
01364     GetJulianDayFromEraYearMonthDay(&firstWeek, changeover);
01365 
01366     /*
01367      * Find Monday of week 1.
01368      */
01369 
01370     firstMonday = WeekdayOnOrBefore(1, firstWeek.julianDay);
01371 
01372     /*
01373      * Advance to the given week and day.
01374      */
01375 
01376     fields->julianDay = firstMonday + 7 * (fields->iso8601Week - 1)
01377             + fields->dayOfWeek - 1;
01378 }
01379 
01380 /*
01381  *----------------------------------------------------------------------
01382  *
01383  * GetJulianDayFromEraYearMonthDay --
01384  *
01385  *      Given era, year, month, and dayOfMonth (in TclDateFields), and the
01386  *      Gregorian transition date, computes the Julian Day Number.
01387  *
01388  * Results:
01389  *      None.
01390  *
01391  * Side effects:
01392  *      Stores day number in 'julianDay'
01393  *
01394  *----------------------------------------------------------------------
01395  */
01396 
01397 static void
01398 GetJulianDayFromEraYearMonthDay(
01399     TclDateFields* fields,      /* Date to convert */
01400     int changeover)             /* Gregorian transition date as a Julian Day */
01401 {
01402     int year;  int ym1;
01403     int month; int mm1;
01404     int q; int r;
01405     int ym1o4; int ym1o100; int ym1o400;
01406 
01407     if (fields->era == BCE) {
01408         year = 1 - fields->year;
01409     } else {
01410         year = fields->year;
01411     }
01412 
01413     /*
01414      * Reduce month modulo 12.
01415      */
01416 
01417     month = fields->month;
01418     mm1 = month - 1;
01419     q = mm1 / 12;
01420     r = (mm1 % 12);
01421     if (r < 0) {
01422         r += 12;
01423         q -= 1;
01424     }
01425     year += q;
01426     month = r + 1;
01427     ym1 = year - 1;
01428 
01429     /*
01430      * Adjust the year after reducing the month.
01431      */
01432 
01433     fields->gregorian = 1;
01434     if (year < 1) {
01435         fields->era = BCE;
01436         fields->year = 1-year;
01437     } else {
01438         fields->era = CE;
01439         fields->year = year;
01440     }
01441 
01442     /*
01443      * Try an initial conversion in the Gregorian calendar.
01444      */
01445 
01446     ym1o4 = ym1 / 4;
01447     if (ym1 % 4 < 0) {
01448         --ym1o4;
01449     }
01450     ym1o100 = ym1 / 100;
01451     if (ym1 % 100 < 0) {
01452         --ym1o100;
01453     }
01454     ym1o400 = ym1 / 400;
01455     if (ym1 % 400 < 0) {
01456         --ym1o400;
01457     }
01458     fields->julianDay = JDAY_1_JAN_1_CE_GREGORIAN - 1
01459             + fields->dayOfMonth
01460             + daysInPriorMonths[IsGregorianLeapYear(fields)][month - 1]
01461             + (ONE_YEAR * ym1)
01462             + ym1o4
01463             - ym1o100
01464             + ym1o400;
01465 
01466     /*
01467      * If the resulting date is before the Gregorian changeover, convert in
01468      * the Julian calendar instead.
01469      */
01470 
01471     if (fields->julianDay < changeover) {
01472         fields->gregorian = 0;
01473         fields->julianDay = JDAY_1_JAN_1_CE_JULIAN - 1
01474                 + fields->dayOfMonth
01475                 + daysInPriorMonths[year%4 == 0][month - 1]
01476                 + (365 * ym1)
01477                 + ym1o4;
01478     }
01479 }
01480 
01481 /*
01482  *----------------------------------------------------------------------
01483  *
01484  * IsGregorianLeapYear --
01485  *
01486  *      Tests whether a given year is a leap year, in either Julian or
01487  *      Gregorian calendar.
01488  *
01489  * Results:
01490  *      Returns 1 for a leap year, 0 otherwise.
01491  *
01492  *----------------------------------------------------------------------
01493  */
01494 
01495 static int
01496 IsGregorianLeapYear(
01497     TclDateFields* fields)      /* Date to test */
01498 {
01499     int year;
01500 
01501     if (fields->era == BCE) {
01502         year = 1 - fields->year;
01503     } else {
01504         year = fields->year;
01505     }
01506     if (year%4 != 0) {
01507         return 0;
01508     } else if (!(fields->gregorian)) {
01509         return 1;
01510     } else if (year%400 == 0) {
01511         return 1;
01512     } else if (year%100 == 0) {
01513         return 0;
01514     } else {
01515         return 1;
01516     }
01517 }
01518 
01519 /*
01520  *----------------------------------------------------------------------
01521  *
01522  * WeekdayOnOrBefore --
01523  *
01524  *      Finds the Julian Day Number of a given day of the week that falls on
01525  *      or before a given date, expressed as Julian Day Number.
01526  *
01527  * Results:
01528  *      Returns the Julian Day Number
01529  *
01530  *----------------------------------------------------------------------
01531  */
01532 
01533 static int
01534 WeekdayOnOrBefore(
01535     int dayOfWeek,              /* Day of week; Sunday == 0 or 7 */
01536     int julianDay)              /* Reference date */
01537 {
01538     int k = (dayOfWeek + 6) % 7;
01539     if (k < 0) {
01540         k += 7;
01541     }
01542     return julianDay - ((julianDay - k) % 7);
01543 }
01544 
01545 /*
01546  *----------------------------------------------------------------------
01547  *
01548  * ClockGetenvObjCmd --
01549  *
01550  *      Tcl command that reads an environment variable from the system
01551  *
01552  * Usage:
01553  *      ::tcl::clock::getEnv NAME
01554  *
01555  * Parameters:
01556  *      NAME - Name of the environment variable desired
01557  *
01558  * Results:
01559  *      Returns a standard Tcl result. Returns an error if the variable does
01560  *      not exist, with a message left in the interpreter. Returns TCL_OK and
01561  *      the value of the variable if the variable does exist,
01562  *
01563  *----------------------------------------------------------------------
01564  */
01565 
01566 int
01567 ClockGetenvObjCmd(
01568     ClientData clientData,
01569     Tcl_Interp* interp,
01570     int objc,
01571     Tcl_Obj *const objv[])
01572 {
01573     const char* varName;
01574     const char* varValue;
01575 
01576     if (objc != 2) {
01577         Tcl_WrongNumArgs(interp, 1, objv, "name");
01578         return TCL_ERROR;
01579     }
01580     varName = TclGetString(objv[1]);
01581     varValue = getenv(varName);
01582     if (varValue == NULL) {
01583         varValue = "";
01584     }
01585     Tcl_SetObjResult(interp, Tcl_NewStringObj(varValue, -1));
01586     return TCL_OK;
01587 }
01588 
01589 /*
01590  *----------------------------------------------------------------------
01591  *
01592  * ThreadSafeLocalTime --
01593  *
01594  *      Wrapper around the 'localtime' library function to make it thread
01595  *      safe.
01596  *
01597  * Results:
01598  *      Returns a pointer to a 'struct tm' in thread-specific data.
01599  *
01600  * Side effects:
01601  *      Invokes localtime or localtime_r as appropriate.
01602  *
01603  *----------------------------------------------------------------------
01604  */
01605 
01606 static struct tm *
01607 ThreadSafeLocalTime(
01608     const time_t *timePtr)      /* Pointer to the number of seconds since the
01609                                  * local system's epoch */
01610 {
01611     /*
01612      * Get a thread-local buffer to hold the returned time.
01613      */
01614 
01615     struct tm *tmPtr = (struct tm *)
01616             Tcl_GetThreadData(&tmKey, (int) sizeof(struct tm));
01617 #ifdef HAVE_LOCALTIME_R
01618     localtime_r(timePtr, tmPtr);
01619 #else
01620     struct tm *sysTmPtr;
01621 
01622     Tcl_MutexLock(&clockMutex);
01623     sysTmPtr = localtime(timePtr);
01624     if (sysTmPtr == NULL) {
01625         Tcl_MutexUnlock(&clockMutex);
01626         return NULL;
01627     } else {
01628         memcpy((void *) tmPtr, (void *) localtime(timePtr), sizeof(struct tm));
01629         Tcl_MutexUnlock(&clockMutex);
01630     }
01631 #endif
01632     return tmPtr;
01633 }
01634 
01635 /*----------------------------------------------------------------------
01636  *
01637  * ClockClicksObjCmd --
01638  *
01639  *      Returns a high-resolution counter.
01640  *
01641  * Results:
01642  *      Returns a standard Tcl result.
01643  *
01644  * Side effects:
01645  *      None.
01646  *
01647  * This function implements the 'clock clicks' Tcl command. Refer to the user
01648  * documentation for details on what it does.
01649  *
01650  *----------------------------------------------------------------------
01651  */
01652 
01653 int
01654 ClockClicksObjCmd(
01655     ClientData clientData,      /* Client data is unused */
01656     Tcl_Interp* interp,         /* Tcl interpreter */
01657     int objc,                   /* Parameter count */
01658     Tcl_Obj* const* objv)       /* Parameter values */
01659 {
01660     static const char *clicksSwitches[] = {
01661         "-milliseconds", "-microseconds", NULL
01662     };
01663     enum ClicksSwitch {
01664         CLICKS_MILLIS,   CLICKS_MICROS,   CLICKS_NATIVE
01665     };
01666     int index = CLICKS_NATIVE;
01667     Tcl_Time now;
01668 
01669     switch (objc) {
01670     case 1:
01671         break;
01672     case 2:
01673         if (Tcl_GetIndexFromObj(interp, objv[1], clicksSwitches, "option", 0,
01674                 &index) != TCL_OK) {
01675             return TCL_ERROR;
01676         }
01677         break;
01678     default:
01679         Tcl_WrongNumArgs(interp, 1, objv, "?option?");
01680         return TCL_ERROR;
01681     }
01682 
01683     switch (index) {
01684     case CLICKS_MILLIS:
01685         Tcl_GetTime(&now);
01686         Tcl_SetObjResult(interp, Tcl_NewWideIntObj((Tcl_WideInt)
01687                 now.sec * 1000 + now.usec / 1000));
01688         break;
01689     case CLICKS_NATIVE: {
01690 #ifndef TCL_WIDE_CLICKS
01691         unsigned long clicks = TclpGetClicks();
01692 #else
01693         Tcl_WideInt clicks = TclpGetWideClicks();
01694 #endif
01695         Tcl_SetObjResult(interp, Tcl_NewWideIntObj((Tcl_WideInt) clicks));
01696         break;
01697     }
01698     case CLICKS_MICROS:
01699         Tcl_GetTime(&now);
01700         Tcl_SetObjResult(interp, Tcl_NewWideIntObj(
01701                 ((Tcl_WideInt) now.sec * 1000000) + now.usec));
01702         break;
01703     }
01704 
01705     return TCL_OK;
01706 }
01707 
01708 /*----------------------------------------------------------------------
01709  *
01710  * ClockMillisecondsObjCmd -
01711  *
01712  *      Returns a count of milliseconds since the epoch.
01713  *
01714  * Results:
01715  *      Returns a standard Tcl result.
01716  *
01717  * Side effects:
01718  *      None.
01719  *
01720  * This function implements the 'clock milliseconds' Tcl command. Refer to the
01721  * user documentation for details on what it does.
01722  *
01723  *----------------------------------------------------------------------
01724  */
01725 
01726 int
01727 ClockMillisecondsObjCmd(
01728     ClientData clientData,      /* Client data is unused */
01729     Tcl_Interp* interp,         /* Tcl interpreter */
01730     int objc,                   /* Parameter count */
01731     Tcl_Obj* const* objv)       /* Parameter values */
01732 {
01733     Tcl_Time now;
01734 
01735     if (objc != 1) {
01736         Tcl_WrongNumArgs(interp, 1, objv, NULL);
01737         return TCL_ERROR;
01738     }
01739     Tcl_GetTime(&now);
01740     Tcl_SetObjResult(interp, Tcl_NewWideIntObj( (Tcl_WideInt)
01741             now.sec * 1000 + now.usec / 1000));
01742     return TCL_OK;
01743 }
01744 
01745 /*----------------------------------------------------------------------
01746  *
01747  * ClockMicrosecondsObjCmd -
01748  *
01749  *      Returns a count of microseconds since the epoch.
01750  *
01751  * Results:
01752  *      Returns a standard Tcl result.
01753  *
01754  * Side effects:
01755  *      None.
01756  *
01757  * This function implements the 'clock microseconds' Tcl command. Refer to the
01758  * user documentation for details on what it does.
01759  *
01760  *----------------------------------------------------------------------
01761  */
01762 
01763 int
01764 ClockMicrosecondsObjCmd(
01765     ClientData clientData,      /* Client data is unused */
01766     Tcl_Interp* interp,         /* Tcl interpreter */
01767     int objc,                   /* Parameter count */
01768     Tcl_Obj* const* objv)       /* Parameter values */
01769 {
01770     Tcl_Time now;
01771 
01772     if (objc != 1) {
01773         Tcl_WrongNumArgs(interp, 1, objv, NULL);
01774         return TCL_ERROR;
01775     }
01776     Tcl_GetTime(&now);
01777     Tcl_SetObjResult(interp, Tcl_NewWideIntObj(
01778             ((Tcl_WideInt) now.sec * 1000000) + now.usec));
01779     return TCL_OK;
01780 }
01781 
01782 /*----------------------------------------------------------------------
01783  *
01784  * ClockSecondsObjCmd -
01785  *
01786  *      Returns a count of microseconds since the epoch.
01787  *
01788  * Results:
01789  *      Returns a standard Tcl result.
01790  *
01791  * Side effects:
01792  *      None.
01793  *
01794  * This function implements the 'clock seconds' Tcl command. Refer to the user
01795  * documentation for details on what it does.
01796  *
01797  *----------------------------------------------------------------------
01798  */
01799 
01800 int
01801 ClockSecondsObjCmd(
01802     ClientData clientData,      /* Client data is unused */
01803     Tcl_Interp* interp,         /* Tcl interpreter */
01804     int objc,                   /* Parameter count */
01805     Tcl_Obj* const* objv)       /* Parameter values */
01806 {
01807     Tcl_Time now;
01808 
01809     if (objc != 1) {
01810         Tcl_WrongNumArgs(interp, 1, objv, NULL);
01811         return TCL_ERROR;
01812     }
01813     Tcl_GetTime(&now);
01814     Tcl_SetObjResult(interp, Tcl_NewWideIntObj((Tcl_WideInt) now.sec));
01815     return TCL_OK;
01816 }
01817 
01818 /*
01819  *----------------------------------------------------------------------
01820  *
01821  * TzsetIfNecessary --
01822  *
01823  *      Calls the tzset() library function if the contents of the TZ
01824  *      environment variable has changed.
01825  *
01826  * Results:
01827  *      None.
01828  *
01829  * Side effects:
01830  *      Calls tzset.
01831  *
01832  *----------------------------------------------------------------------
01833  */
01834 
01835 static void
01836 TzsetIfNecessary(void)
01837 {
01838     static char* tzWas = NULL;  /* Previous value of TZ, protected by
01839                                  * clockMutex. */
01840     const char* tzIsNow;        /* Current value of TZ */
01841 
01842     Tcl_MutexLock(&clockMutex);
01843     tzIsNow = getenv("TZ");
01844     if (tzIsNow != NULL && (tzWas == NULL || strcmp(tzIsNow, tzWas) != 0)) {
01845         tzset();
01846         if (tzWas != NULL) {
01847             ckfree(tzWas);
01848         }
01849         tzWas = ckalloc(strlen(tzIsNow) + 1);
01850         strcpy(tzWas, tzIsNow);
01851     } else if (tzIsNow == NULL && tzWas != NULL) {
01852         tzset();
01853         ckfree(tzWas);
01854         tzWas = NULL;
01855     }
01856     Tcl_MutexUnlock(&clockMutex);
01857 }
01858 
01859 /*
01860  *----------------------------------------------------------------------
01861  *
01862  * ClockDeleteCmdProc --
01863  *
01864  *      Remove a reference to the clock client data, and clean up memory
01865  *      when it's all gone.
01866  *
01867  * Results:
01868  *      None.
01869  *
01870  *----------------------------------------------------------------------
01871  */
01872 
01873 static void
01874 ClockDeleteCmdProc(
01875     ClientData clientData)      /* Opaque pointer to the client data */
01876 {
01877     ClockClientData *data = (ClockClientData*) clientData;
01878     int i;
01879 
01880     --(data->refCount);
01881     if (data->refCount == 0) {
01882         for (i = 0; i < LIT__END; ++i) {
01883             Tcl_DecrRefCount(data->literals[i]);
01884         }
01885         ckfree((char*) (data->literals));
01886         ckfree((char*) data);
01887     }
01888 }
01889 
01890 /*
01891  * Local Variables:
01892  * mode: c
01893  * c-basic-offset: 4
01894  * fill-column: 78
01895  * End:
01896  */



Generated on Wed Mar 12 12:18:12 2008 by  doxygen 1.5.1