summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKevin B Kenny <kennykb@acm.org>2005-11-29 22:50:53 (GMT)
committerKevin B Kenny <kennykb@acm.org>2005-11-29 22:50:53 (GMT)
commitc474eb0cfebfaf5706ed2396bea6b5cb7b9437d9 (patch)
tree214c594a598d95dd6810c54e15734ddbc65268f0
parentee5f76eeacd881cb235705efa89282157cceeed4 (diff)
downloadtcl-c474eb0cfebfaf5706ed2396bea6b5cb7b9437d9.zip
tcl-c474eb0cfebfaf5706ed2396bea6b5cb7b9437d9.tar.gz
tcl-c474eb0cfebfaf5706ed2396bea6b5cb7b9437d9.tar.bz2
make [clock format] go faster
-rw-r--r--ChangeLog7
-rw-r--r--generic/tclBasic.c20
-rw-r--r--generic/tclClock.c1402
-rw-r--r--generic/tclInt.h24
-rw-r--r--library/clock.tcl678
5 files changed, 1317 insertions, 814 deletions
diff --git a/ChangeLog b/ChangeLog
index 83bd957..e7d89d4 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,10 @@
+2005-11-29 Kevin Kenny <kennykb@acm.org>
+
+ * generic/tclBasic.c: Moved a big part of [clock format] down
+ * generic/tclClock.c: to the C level in order to make it go faster.
+ * generic/tclInt.h: Preliminary measurements suggest that it
+ * generic/clock.tcl: more than doubles in speed with this change.
+
2005-11-29 Donal K. Fellows <donal.k.fellows@manchester.ac.uk>
* generic/tclCmdIL.c (Tcl_LsearchObjCmd): Allow [lsearch -regexp] to
diff --git a/generic/tclBasic.c b/generic/tclBasic.c
index c5c842d..6bd2627 100644
--- a/generic/tclBasic.c
+++ b/generic/tclBasic.c
@@ -13,7 +13,7 @@
* See the file "license.terms" for information on usage and redistribution of
* this file, and for a DISCLAIMER OF ALL WARRANTIES.
*
- * RCS: @(#) $Id: tclBasic.c,v 1.182 2005/11/14 16:45:11 dgp Exp $
+ * RCS: @(#) $Id: tclBasic.c,v 1.183 2005/11/29 22:50:56 kennykb Exp $
*/
#include "tclInt.h"
@@ -466,22 +466,8 @@ Tcl_CreateInterp(void)
* Tcl_CreateObjCommand, since they aren't in the global namespace.
*/
- Tcl_CreateObjCommand(interp, "::tcl::clock::clicks",
- TclClockClicksObjCmd, (ClientData) NULL, NULL);
- Tcl_CreateObjCommand(interp, "::tcl::clock::getenv",
- TclClockGetenvObjCmd, (ClientData) NULL, NULL);
- Tcl_CreateObjCommand(interp, "::tcl::clock::microseconds",
- TclClockMicrosecondsObjCmd, (ClientData) NULL, NULL);
- Tcl_CreateObjCommand(interp, "::tcl::clock::milliseconds",
- TclClockMillisecondsObjCmd, (ClientData) NULL, NULL);
- Tcl_CreateObjCommand(interp, "::tcl::clock::seconds",
- TclClockSecondsObjCmd, (ClientData) NULL, NULL);
- Tcl_CreateObjCommand(interp, "::tcl::clock::Localtime",
- TclClockLocaltimeObjCmd, (ClientData) NULL, NULL);
- Tcl_CreateObjCommand(interp, "::tcl::clock::Mktime",
- TclClockMktimeObjCmd, (ClientData) NULL, NULL);
- Tcl_CreateObjCommand(interp, "::tcl::clock::Oldscan",
- TclClockOldscanObjCmd, (ClientData) NULL, NULL);
+ TclClockInit(interp);
+
/* TIP #208 */
Tcl_CreateObjCommand(interp, "::tcl::chan::Truncate",
TclChanTruncateObjCmd, (ClientData) NULL, NULL);
diff --git a/generic/tclClock.c b/generic/tclClock.c
index 71dec02..3dc93fa 100644
--- a/generic/tclClock.c
+++ b/generic/tclClock.c
@@ -12,7 +12,7 @@
* See the file "license.terms" for information on usage and redistribution of
* this file, and for a DISCLAIMER OF ALL WARRANTIES.
*
- * RCS: @(#) $Id: tclClock.c,v 1.42 2005/11/01 15:30:52 dkf Exp $
+ * RCS: @(#) $Id: tclClock.c,v 1.43 2005/11/29 22:50:58 kennykb Exp $
*/
#include "tclInt.h"
@@ -26,6 +26,92 @@
#endif
/*
+ * Constants
+ */
+
+#define JULIAN_SEC_POSIX_EPOCH ((Tcl_WideInt) 210866803200)
+#define SECONDS_PER_DAY 86400
+#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_BCE, LIT_CE,
+ LIT_DAYOFMONTH, LIT_DAYOFWEEK, LIT_DAYOFYEAR,
+ LIT_ERA, LIT_GREGORIAN,
+ 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[] = {
+ "BCE", "CE",
+ "dayOfMonth", "dayOfWeek", "dayOfYear",
+ "era", "gregorian",
+ "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;
+
+/*
* Thread specific data block holding a 'struct tm' for the 'gmtime' and
* 'localtime' library calls.
*/
@@ -43,312 +129,1216 @@ TCL_DECLARE_MUTEX(clockMutex)
* Function prototypes for local procedures in this file:
*/
+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 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 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);
+
/*
*----------------------------------------------------------------------
*
- * TclClockGetenvObjCmd --
+ * TclClockInit --
*
- * Tcl command that reads an environment variable from the system
+ * Registers the 'clock' subcommands with the Tcl interpreter
+ * and initializes its client data (which consists mostly of
+ * constant Tcl_Obj's that it is too much trouble to keep
+ * recreating).
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * Installs the commands and creates the client data
+ *
+ *----------------------------------------------------------------------
+ */
+
+void
+TclClockInit(
+ Tcl_Interp* interp /* Tcl interpreter */
+) {
+ int i;
+
+ /* Create the client data */
+
+ ClockClientData *data =
+ (ClockClientData*) ckalloc(sizeof(ClockClientData));
+ data->refCount = 0;
+
+ /*
+ * Create the literal pool
+ */
+ 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 */
+
+ Tcl_CreateObjCommand(interp, "::tcl::clock::clicks",
+ ClockClicksObjCmd, (ClientData) NULL, NULL);
+ Tcl_CreateObjCommand(interp, "::tcl::clock::getenv",
+ ClockGetenvObjCmd, (ClientData) NULL, NULL);
+ Tcl_CreateObjCommand(interp, "::tcl::clock::microseconds",
+ ClockMicrosecondsObjCmd, (ClientData) NULL, NULL);
+ Tcl_CreateObjCommand(interp, "::tcl::clock::milliseconds",
+ ClockMillisecondsObjCmd, (ClientData) NULL, NULL);
+ Tcl_CreateObjCommand(interp, "::tcl::clock::seconds",
+ ClockSecondsObjCmd, (ClientData) NULL, NULL);
+ Tcl_CreateObjCommand(interp, "::tcl::clock::Oldscan",
+ TclClockOldscanObjCmd, (ClientData) NULL, NULL);
+ Tcl_CreateObjCommand(interp, "::tcl::clock::ConvertLocalToUTC",
+ ClockConvertlocaltoutcObjCmd, (ClientData) data,
+ ClockDeleteCmdProc);
+ Tcl_CreateObjCommand(interp, "::tcl::clock::GetDateFields",
+ ClockGetdatefieldsObjCmd,(ClientData) data,
+ ClockDeleteCmdProc);
+ ++data->refCount;
+
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * ClockConvertlocaltoutcObjCmd --
+ *
+ * Tcl command that converts a UTC time to a local time by
+ * whatever means is available.
*
* Usage:
- * ::tcl::clock::getEnv NAME
+ * ::tcl::clock::ConvertUTCToLocal dictionary tzdata changeover
*
* Parameters:
- * NAME - Name of the environment variable desired
+ * 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. 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,
+ * 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.
*
*----------------------------------------------------------------------
*/
-int
-TclClockGetenvObjCmd(
- ClientData clientData,
- Tcl_Interp* interp,
- int objc,
- Tcl_Obj *CONST objv[])
-{
+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;
- CONST char* varName;
- CONST char* varValue;
+ /* Check params and convert time */
- if (objc != 2) {
- Tcl_WrongNumArgs(interp, 1, objv, "name");
+ if (objc != 4) {
+ Tcl_WrongNumArgs(interp, 1, objv, "dict tzdata changeover");
return TCL_ERROR;
}
- varName = Tcl_GetStringFromObj(objv[1], NULL);
- varValue = getenv(varName);
- if (varValue == NULL) {
- Tcl_SetObjResult(interp,
- Tcl_NewStringObj("variable not found", -1));
+ dict = objv[1];
+ if ((Tcl_DictObjGet(interp, dict, literals[LIT_LOCALSECONDS], &secondsObj)
+ != TCL_OK)
+ || (Tcl_GetWideIntFromObj(interp, secondsObj, &(fields.localSeconds))
+ != TCL_OK)
+ || (Tcl_GetIntFromObj(interp, objv[3], &changeover) != TCL_OK)
+ || ConvertLocalToUTC(interp, &fields, objv[2], changeover)) {
return TCL_ERROR;
- } else {
- Tcl_SetObjResult(interp, Tcl_NewStringObj(varValue, -1));
- return TCL_OK;
}
+
+ /*
+ * 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;
}
/*
- *-------------------------------------------------------------------------
+ *----------------------------------------------------------------------
*
- * TclClockLocaltimeObjCmd --
+ * ClockGetdatefieldsObjCmd --
*
- * Tcl command that extracts local time using the C library to do
- * it.
+ * Tcl command that determines the values that [clock format] will
+ * use in formatting a date, and populates a dictionary with them.
*
* Usage:
- * ::tcl::clock::Localtime <tick>
+ * ::tcl::clock::GetDateFields seconds tzdata changeover
*
* Parameters:
- * <tick> -- A count of seconds from the Posix epoch.
+ * 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
+ClockGetdatefieldsObjCmd(
+ 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;
+ int changeover;
- * Returns a standard Tcl result. The object result is a Tcl
- * list containing the year, month, day, hour, minute, and second
- * fields of the local time. It may return an error if the
- * argument exceeds the arithmetic range representable by
- * 'time_t'.
+ /* Check params */
+
+ if (objc != 4) {
+ Tcl_WrongNumArgs(interp, 1, objv, "seconds tzdata changeover");
+ return TCL_ERROR;
+ }
+ if (Tcl_GetWideIntFromObj(interp, objv[1], &(fields.seconds)) != TCL_OK
+ || Tcl_GetIntFromObj(interp, objv[3], &changeover) != TCL_OK) {
+ 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((Tcl_Interp*) NULL, dict, literals[LIT_LOCALSECONDS],
+ Tcl_NewWideIntObj(fields.localSeconds));
+ Tcl_DictObjPut((Tcl_Interp*) NULL, dict, literals[LIT_SECONDS],
+ Tcl_NewWideIntObj(fields.seconds));
+ Tcl_DictObjPut((Tcl_Interp*) NULL, dict, literals[LIT_TZNAME],
+ fields.tzName);
+ Tcl_DecrRefCount(fields.tzName);
+ Tcl_DictObjPut((Tcl_Interp*) NULL, dict, literals[LIT_TZOFFSET],
+ Tcl_NewIntObj(fields.tzOffset));
+ Tcl_DictObjPut((Tcl_Interp*) NULL, dict, literals[LIT_JULIANDAY],
+ Tcl_NewWideIntObj(fields.julianDay));
+ Tcl_DictObjPut((Tcl_Interp*) NULL, dict, literals[LIT_GREGORIAN],
+ Tcl_NewIntObj(fields.gregorian));
+ Tcl_DictObjPut((Tcl_Interp*) NULL, dict, literals[LIT_ERA],
+ literals[fields.era ? LIT_BCE : LIT_CE]);
+ Tcl_DictObjPut((Tcl_Interp*) NULL, dict, literals[LIT_YEAR],
+ Tcl_NewIntObj(fields.year));
+ Tcl_DictObjPut((Tcl_Interp*) NULL, dict, literals[LIT_DAYOFYEAR],
+ Tcl_NewIntObj(fields.dayOfYear));
+ Tcl_DictObjPut((Tcl_Interp*) NULL, dict, literals[LIT_MONTH],
+ Tcl_NewIntObj(fields.month));
+ Tcl_DictObjPut((Tcl_Interp*) NULL, dict, literals[LIT_DAYOFMONTH],
+ Tcl_NewIntObj(fields.dayOfMonth));
+ Tcl_DictObjPut((Tcl_Interp*) NULL, dict, literals[LIT_ISO8601YEAR],
+ Tcl_NewIntObj(fields.iso8601Year));
+ Tcl_DictObjPut((Tcl_Interp*) NULL, dict, literals[LIT_ISO8601WEEK],
+ Tcl_NewIntObj(fields.iso8601Week));
+ Tcl_DictObjPut((Tcl_Interp*) NULL, dict, literals[LIT_DAYOFWEEK],
+ Tcl_NewIntObj(fields.dayOfWeek));
+ Tcl_SetObjResult(interp, dict);
+
+ return TCL_OK;
+}
+
+/*
+ *----------------------------------------------------------------------
*
- * Side effects:
- * None.
+ * ConvertLocalToUTC --
+ *
+ * Converts a time (in a TclDateFields structure) from the
+ * local wall clock to UTC.
+ *
+ * Results:
+ * Returns a standard Tcl result.
*
- * This function is used as a call of last resort if the current time
- * zone cannot be determined from environment variables TZ or TCL_TZ.
- * It attempts to use the 'localtime' library function to extract the
- * time and return it that way. This method suffers from Y2038 problems
- * on most platforms. It also provides no portable way to get the
- * name of the time zone.
+ * Side effects:
+ * Populates the 'seconds' field if successful; stores an error
+ * message in the interpreter result on failure.
*
- *-------------------------------------------------------------------------
+ *----------------------------------------------------------------------
*/
-int
-TclClockLocaltimeObjCmd(
- ClientData clientData, /* Unused */
+static int
+ConvertLocalToUTC(
Tcl_Interp* interp, /* Tcl interpreter */
- int objc, /* Parameter count */
- Tcl_Obj* CONST* objv) /* Parameter vector */
-{
- Tcl_WideInt tick; /* Time to convert */
- time_t tock;
- struct tm* timeVal; /* Time after conversion */
+ 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 */
- Tcl_Obj* returnVec[ 6 ];
+ /* unpack the tz data */
+
+ if (Tcl_ListObjGetElements(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;
/*
- * Check args
+ * 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.
*/
- if (objc != 2) {
- Tcl_WrongNumArgs(interp, 1, objv, "seconds");
+ found = 0;
+ fields->tzOffset = 0;
+ fields->seconds = fields->localSeconds;
+ while (!found) {
+ row = LookupLastTransition(interp, fields->seconds, rowc, rowv);
+ if ((row == NULL)
+ || (Tcl_ListObjGetElements(interp, row, &cellc, &cellv) != TCL_OK)
+ || (Tcl_GetIntFromObj(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 (!found) {
+ if (nHave == 8) {
+ Tcl_Panic("loop in ConvertLocalToUTCUsingTable");
+ }
+ 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.
+ *
+ *----------------------------------------------------------------------
+ */
+
+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;
+
+ /* Convert the given time to a date */
+
+ fields->julianDay = (int) ((fields->localSeconds + JULIAN_SEC_POSIX_EPOCH)
+ / SECONDS_PER_DAY);
+ GetGregorianEraYearDay(fields, changeover);
+ GetMonthDay(fields);
+
+ /* Convert the date/time to a 'struct tm' */
+
+ timeVal.tm_year = fields->year - 1900;
+ timeVal.tm_mon = fields->month - 1;
+ timeVal.tm_mday = fields->dayOfMonth;
+ timeVal.tm_hour = (int)((fields->localSeconds / 3600) % 24);
+ timeVal.tm_min = (int)((fields->localSeconds / 60) % 60);
+ timeVal.tm_sec = (int)(fields->localSeconds % 60);
+ timeVal.tm_isdst = -1;
+ timeVal.tm_wday = -1;
+ timeVal.tm_yday = -1;
+
+ /*
+ * Get local time. It is rumored that mktime is not thread safe
+ * on some platforms, so seize a mutex before attempting this.
+ */
+
+ TzsetIfNecessary();
+ Tcl_MutexLock(&clockMutex);
+ errno = 0;
+ fields->seconds = (Tcl_WideInt) mktime(&timeVal);
+ localErrno = errno;
+ Tcl_MutexUnlock(&clockMutex);
+
+ /* If conversion fails, report an error */
+
+ if (localErrno != 0
+ || (fields->seconds == -1 && timeVal.tm_yday == -1)) {
+ Tcl_SetObjResult(interp,
+ Tcl_NewStringObj( "time value too large/small to "
+ "represent", -1));
return TCL_ERROR;
}
- if (Tcl_GetWideIntFromObj(interp, objv[1], &tick) != TCL_OK) {
+ return TCL_OK;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * ConvertUTCToLocal --
+ *
+ * Converts a time (in a TclDateFields structure) from UTC to
+ * local time.
+ *
+ * Results:
+ * Returns a standard Tcl result.
+ *
+ * Side effects:
+ * Populates the 'tzName' and 'tzOffset' fields.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static int
+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 */
+) {
+ int rowc; /* Number of rows in tzdata */
+ Tcl_Obj** rowv; /* Pointers to the rows */
+
+ /* unpack the tz data */
+
+ if (Tcl_ListObjGetElements(interp, tzdata, &rowc, &rowv) != TCL_OK) {
return TCL_ERROR;
}
- /*
- * Convert the time, checking for overflow
+ /*
+ * Special case: If the time zone is :localtime, the tzdata will be empty.
+ * Use 'localtime' to convert the time to local
*/
- tock = (time_t) tick;
- if ((Tcl_WideInt) tock != tick) {
+ 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
+ || (Tcl_ListObjGetElements(interp, row, &cellc, &cellv) != TCL_OK)
+ || (Tcl_GetIntFromObj(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);
+ "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_AppendResult(interp,
+ "localtime failed (clock value may be too ",
+ "large/small to represent)", NULL);
Tcl_SetErrorCode(interp, "CLOCK", "localtimeFailed", NULL);
return TCL_ERROR;
}
- /*
- * Package the results.
- */
+ /* 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);
- returnVec[0] = Tcl_NewIntObj(timeVal->tm_year + 1900);
- returnVec[1] = Tcl_NewIntObj(timeVal->tm_mon + 1);
- returnVec[2] = Tcl_NewIntObj(timeVal->tm_mday);
- returnVec[3] = Tcl_NewIntObj(timeVal->tm_hour);
- returnVec[4] = Tcl_NewIntObj(timeVal->tm_min);
- returnVec[5] = Tcl_NewIntObj(timeVal->tm_sec);
- Tcl_SetObjResult(interp, Tcl_NewListObj(6, returnVec));
+ /* 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;
}
/*
*----------------------------------------------------------------------
*
- * ThreadSafeLocalTime --
+ * LookupLastTransition --
*
- * Wrapper around the 'localtime' library function to make it thread
- * safe.
+ * Given a UTC time and a tzdata array, looks up the last
+ * transition on or before the given time.
*
* Results:
- * Returns a pointer to a 'struct tm' in thread-specific data.
- *
- * Side effects:
- * Invokes localtime or localtime_r as appropriate.
+ * Returns a pointer to the row, or NULL if an error occurs.
*
*----------------------------------------------------------------------
*/
-static struct tm *
-ThreadSafeLocalTime(
- CONST time_t *timePtr) /* Pointer to the number of seconds since the
- * local system's epoch */
+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 */
{
- /*
- * Get a thread-local buffer to hold the returned time.
- */
+ int l;
+ int u;
+ Tcl_Obj* compObj;
+ Tcl_WideInt compVal;
- struct tm *tmPtr = (struct tm *)
- Tcl_GetThreadData(&tmKey, (int) sizeof(struct tm));
-#ifdef HAVE_LOCALTIME_R
- localtime_r(timePtr, tmPtr);
-#else
- struct tm *sysTmPtr;
+ /* Examine the first row to make sure we're in bounds */
- Tcl_MutexLock(&clockMutex);
- sysTmPtr = localtime(timePtr);
- if (sysTmPtr == NULL) {
- Tcl_MutexUnlock(&clockMutex);
+ if (Tcl_ListObjIndex(interp, rowv[0], 0, &compObj) != TCL_OK
+ || Tcl_GetWideIntFromObj(interp, compObj, &compVal) != TCL_OK) {
return NULL;
- } else {
- memcpy((VOID *) tmPtr, (VOID *) localtime(timePtr), sizeof(struct tm));
- Tcl_MutexUnlock(&clockMutex);
}
-#endif
- return tmPtr;
+ /*
+ * Bizarre case - first row doesn't begin at MIN_WIDE_INT.
+ * Return it anyway.
+ */
+ if (tick < compVal) {
+ return rowv[0];
+ }
+
+ /*
+ * Binary-search to find the transition.
+ */
+
+ l = 0;
+ u = rowc-1;
+ while (l < u) {
+ int m = (l + u + 1) / 2;
+ if (Tcl_ListObjIndex(interp, rowv[m], 0, &compObj) != TCL_OK
+ || Tcl_GetWideIntFromObj(interp, compObj, &compVal) != TCL_OK) {
+ return NULL;
+ }
+ if (tick >= compVal) {
+ l = m;
+ } else {
+ u = m-1;
+ }
+ }
+ return rowv[l];
+
}
/*
*----------------------------------------------------------------------
*
- * TclClockMktimeObjCmd --
+ * GetYearWeekDay --
*
- * Determine seconds from the epoch, given the fields of a local time.
+ * Given a date with Julian Calendar Day, compute the year, week,
+ * and day in the ISO8601 calendar.
*
- * Usage:
- * mktime <year> <month> <day> <hour> <minute> <second>
+ * Results:
+ * None.
*
- * Parameters:
- * year -- Calendar year
- * month -- Calendar month
- * day -- Calendar day
- * hour -- Hour of day (00-23)
- * minute -- Minute of hour
- * second -- Second of minute
+ * Side effects:
+ * Stores 'iso8601Year', 'iso8601Week' and 'dayOfWeek' in
+ * the date fields.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+GetYearWeekDay(
+ TclDateFields* fields, /* Date to convert, must have 'julianDay' */
+ int changeover /* Julian Day Number of the Gregorian
+ * transition */
+) {
+ TclDateFields temp;
+ int dayOfFiscalYear;
+
+ /*
+ * Find the given date, minus three days, plus one year. That date's
+ * iso8601 year is an upper bound on the ISO8601 year of the given date.
+ */
+
+ temp.julianDay = fields->julianDay - 3;
+ GetGregorianEraYearDay(&temp, changeover);
+ if (temp.era == BCE) {
+ temp.iso8601Year = temp.year - 1;
+ } else {
+ temp.iso8601Year = temp.year + 1;
+ }
+ temp.iso8601Week = 1;
+ temp.dayOfWeek = 1;
+ GetJulianDayFromEraYearWeekDay(&temp, changeover);
+
+ /*
+ * 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 {
+ temp.iso8601Year -= 1;
+ }
+ GetJulianDayFromEraYearWeekDay(&temp, changeover);
+ }
+
+ fields->iso8601Year = temp.iso8601Year;
+ dayOfFiscalYear = fields->julianDay - temp.julianDay;
+ fields->iso8601Week = (dayOfFiscalYear / 7) + 1;
+ fields->dayOfWeek = (dayOfFiscalYear + 1) % 7;
+ if (fields->dayOfWeek < 1) {
+ fields->dayOfWeek += 7;
+ }
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * GetGregorianEraYearDay --
+ *
+ * Given a Julian Day Number, extracts the year and day of the
+ * year and puts them into TclDateFields, along with the era
+ * (BCE or CE) and a flag indicating whether the date is Gregorian
+ * or Julian.
*
* Results:
- * Returns the given local time.
+ * None.
*
- * Errors:
- * Returns an error if the 'mktime' function does not exist in the C
- * library, or if the given time cannot be converted.
+ * Side effects:
+ * Stores 'era', 'gregorian', 'year', and 'dayOfYear'.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+GetGregorianEraYearDay(
+ TclDateFields* fields, /* Date fields containing 'julianDay' */
+ int changeover /* Gregorian transition date */
+) {
+ int jday = fields->julianDay;
+ int day;
+ int year;
+ int n;
+
+ if (jday >= changeover) {
+
+ /* Gregorian calendar */
+
+ fields->gregorian = 1;
+ year = 1;
+
+ /*
+ * n = Number of 400-year cycles since 1 January, 1 CE in the
+ * proleptic Gregorian calendar. day = remaining days.
+ */
+
+ day = jday - JDAY_1_JAN_1_CE_GREGORIAN;
+ n = day / FOUR_CENTURIES;
+ day %= FOUR_CENTURIES;
+ 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 %= 1461;
+ 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.
+ *
*----------------------------------------------------------------------
*/
-int
-TclClockMktimeObjCmd(
- ClientData clientData, /* Unused */
- Tcl_Interp *interp, /* Tcl interpreter */
- int objc, /* Parameter count */
- Tcl_Obj *CONST *objv) /* Parameter vector */
-{
-#ifndef HAVE_MKTIME
- Tcl_AppendResult(interp, "cannot determine local time", NULL);
- return TCL_ERROR;
-#else
+static void
+GetJulianDayFromEraYearWeekDay(
+ TclDateFields* fields, /* Date to convert */
+ int changeover /* Julian Day Number of the
+ * Gregorian transition */
+) {
- int i;
- struct tm toConvert; /* Time to be converted */
- time_t convertedTime; /* Time converted from mktime */
- int localErrno;
+ int firstMonday; /* Julian day number of week 1, day 1
+ * in the given year */
- /*
- * Convert parameters
- */
+ /* Find January 4 in the ISO8601 year, which will always be in week 1 */
- if (objc != 7) {
- Tcl_WrongNumArgs(interp, 1, objv, "year month day hour minute second");
- return TCL_ERROR;
+ 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;
+
+ if (fields->era == BCE) {
+ year = 1 - fields->year;
+ } else {
+ year = fields->year;
}
- if (Tcl_GetIntFromObj(interp, objv[1], &i) != TCL_OK) {
- return TCL_ERROR;
+
+ /* Reduce month modulo 12 */
+
+ month = fields->month;
+ mm1 = month - 1;
+ year += mm1 / 12;
+ month = (mm1 % 12) + 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;
}
- toConvert.tm_year = i - 1900;
- if (Tcl_GetIntFromObj(interp, objv[2], &i) != TCL_OK) {
- return TCL_ERROR;
+
+ /* Try an initial conversion in the Gregorian calendar */
+
+ fields->julianDay = JDAY_1_JAN_1_CE_GREGORIAN - 1
+ + fields->dayOfMonth
+ + daysInPriorMonths[IsGregorianLeapYear(fields)][month - 1]
+ + (ONE_YEAR * ym1)
+ + (ym1 / 4)
+ - (ym1 / 100)
+ + (ym1 / 400);
+
+ /*
+ * If the resulting date is before the Gregorian changeover, convert
+ * in the Julian calendar instead.
+ */
+
+ if (fields->julianDay < changeover) {
+ fields->gregorian = 0;
+ fields->julianDay = JDAY_1_JAN_1_CE_JULIAN - 1
+ + fields->dayOfMonth
+ + daysInPriorMonths[year%4 == 0][month - 1]
+ + (365 * ym1)
+ + (ym1 / 4);
}
- toConvert.tm_mon = i - 1;
- if (Tcl_GetIntFromObj(interp, objv[3], &i) != TCL_OK) {
- return TCL_ERROR;
+
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * 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;
}
- toConvert.tm_mday = i;
- if (Tcl_GetIntFromObj(interp, objv[4], &i) != TCL_OK) {
- return TCL_ERROR;
+ 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;
}
- toConvert.tm_hour = i;
- if (Tcl_GetIntFromObj(interp, objv[5], &i) != TCL_OK) {
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * 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;
+ 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;
}
- toConvert.tm_min = i;
- if (Tcl_GetIntFromObj(interp, objv[6], &i) != TCL_OK) {
+ varName = Tcl_GetStringFromObj(objv[1], NULL);
+ varValue = getenv(varName);
+ if (varValue == NULL) {
+ Tcl_SetObjResult(interp,
+ Tcl_NewStringObj("variable not found", -1));
return TCL_ERROR;
+ } else {
+ Tcl_SetObjResult(interp, Tcl_NewStringObj(varValue, -1));
+ return TCL_OK;
}
- toConvert.tm_sec = i;
- toConvert.tm_isdst = -1;
- toConvert.tm_wday = -1;
- toConvert.tm_yday = -1;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * 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. It is rumored that mktime is not thread safe on some
- * platforms.
+ * Get a thread-local buffer to hold the returned time.
*/
- TzsetIfNecessary();
- Tcl_MutexLock(&clockMutex);
- errno = 0;
- convertedTime = mktime(&toConvert);
- localErrno = errno;
- Tcl_MutexUnlock(&clockMutex);
-
- /*
- * Return the converted time, or an error if conversion fails.
- */
+ struct tm *tmPtr = (struct tm *)
+ Tcl_GetThreadData(&tmKey, (int) sizeof(struct tm));
+#ifdef HAVE_LOCALTIME_R
+ localtime_r(timePtr, tmPtr);
+#else
+ struct tm *sysTmPtr;
- if (localErrno != 0 || (convertedTime == -1 && toConvert.tm_yday == -1)) {
- Tcl_AppendResult(interp, "time value too large/small to represent",
- NULL);
- return TCL_ERROR;
+ 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);
}
-
- Tcl_SetObjResult(interp, Tcl_NewWideIntObj((Tcl_WideInt) convertedTime));
- return TCL_OK;
-#endif /* HAVE_MKTIME */
+#endif
+ return tmPtr;
}
/*----------------------------------------------------------------------
*
- * TclClockClicksObjCmd --
+ * ClockClicksObjCmd --
*
* Returns a high-resolution counter.
*
@@ -365,7 +1355,7 @@ TclClockMktimeObjCmd(
*/
int
-TclClockClicksObjCmd(
+ClockClicksObjCmd(
ClientData clientData, /* Client data is unused */
Tcl_Interp* interp, /* Tcl interpreter */
int objc, /* Parameter count */
@@ -416,7 +1406,7 @@ TclClockClicksObjCmd(
/*----------------------------------------------------------------------
*
- * TclClockMillisecondsObjCmd -
+ * ClockMillisecondsObjCmd -
*
* Returns a count of milliseconds since the epoch.
*
@@ -433,7 +1423,7 @@ TclClockClicksObjCmd(
*/
int
-TclClockMillisecondsObjCmd(
+ClockMillisecondsObjCmd(
ClientData clientData, /* Client data is unused */
Tcl_Interp* interp, /* Tcl interpreter */
int objc, /* Parameter count */
@@ -453,7 +1443,7 @@ TclClockMillisecondsObjCmd(
/*----------------------------------------------------------------------
*
- * TclClockMicrosecondsObjCmd -
+ * ClockMicrosecondsObjCmd -
*
* Returns a count of microseconds since the epoch.
*
@@ -470,7 +1460,7 @@ TclClockMillisecondsObjCmd(
*/
int
-TclClockMicrosecondsObjCmd(
+ClockMicrosecondsObjCmd(
ClientData clientData, /* Client data is unused */
Tcl_Interp* interp, /* Tcl interpreter */
int objc, /* Parameter count */
@@ -490,7 +1480,7 @@ TclClockMicrosecondsObjCmd(
/*----------------------------------------------------------------------
*
- * TclClockSecondsObjCmd -
+ * ClockSecondsObjCmd -
*
* Returns a count of microseconds since the epoch.
*
@@ -507,7 +1497,7 @@ TclClockMicrosecondsObjCmd(
*/
int
-TclClockSecondsObjCmd(
+ClockSecondsObjCmd(
ClientData clientData, /* Client data is unused */
Tcl_Interp* interp, /* Tcl interpreter */
int objc, /* Parameter count */
@@ -566,6 +1556,36 @@ TzsetIfNecessary(void)
}
/*
+ *----------------------------------------------------------------------
+ *
+ * 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
diff --git a/generic/tclInt.h b/generic/tclInt.h
index 7ad1f25..d04b285 100644
--- a/generic/tclInt.h
+++ b/generic/tclInt.h
@@ -12,7 +12,7 @@
* See the file "license.terms" for information on usage and redistribution of
* this file, and for a DISCLAIMER OF ALL WARRANTIES.
*
- * RCS: @(#) $Id: tclInt.h,v 1.259 2005/11/27 02:33:49 das Exp $
+ * RCS: @(#) $Id: tclInt.h,v 1.260 2005/11/29 22:50:58 kennykb Exp $
*/
#ifndef _TCLINT
@@ -2261,27 +2261,7 @@ MODULE_SCOPE int Tcl_CdObjCmd(ClientData clientData,
MODULE_SCOPE int TclChanTruncateObjCmd(
ClientData clientData, Tcl_Interp *interp,
int objc, Tcl_Obj *CONST objv[]);
-MODULE_SCOPE int TclClockClicksObjCmd(
- ClientData clientData, Tcl_Interp *interp,
- int objc, Tcl_Obj *CONST objv[]);
-MODULE_SCOPE int TclClockGetenvObjCmd(
- ClientData clientData, Tcl_Interp *interp,
- int objc, Tcl_Obj *CONST objv[]);
-MODULE_SCOPE int TclClockMicrosecondsObjCmd(
- ClientData clientData, Tcl_Interp *interp,
- int objc, Tcl_Obj *CONST objv[]);
-MODULE_SCOPE int TclClockMillisecondsObjCmd(
- ClientData clientData, Tcl_Interp *interp,
- int objc, Tcl_Obj *CONST objv[]);
-MODULE_SCOPE int TclClockSecondsObjCmd(
- ClientData clientData, Tcl_Interp *interp,
- int objc, Tcl_Obj *CONST objv[]);
-MODULE_SCOPE int TclClockLocaltimeObjCmd(
- ClientData clientData, Tcl_Interp *interp,
- int objc, Tcl_Obj *CONST objv[]);
-MODULE_SCOPE int TclClockMktimeObjCmd(
- ClientData clientData, Tcl_Interp *interp,
- int objc, Tcl_Obj *CONST objv[]);
+MODULE_SCOPE void TclClockInit(Tcl_Interp*);
MODULE_SCOPE int TclClockOldscanObjCmd(
ClientData clientData, Tcl_Interp *interp,
int objc, Tcl_Obj *CONST objv[]);
diff --git a/library/clock.tcl b/library/clock.tcl
index eaa512d..6f6b1ed 100644
--- a/library/clock.tcl
+++ b/library/clock.tcl
@@ -13,7 +13,7 @@
# See the file "license.terms" for information on usage and redistribution
# of this file, and for a DISCLAIMER OF ALL WARRANTIES.
#
-# RCS: @(#) $Id: clock.tcl,v 1.20 2005/11/04 20:13:30 kennykb Exp $
+# RCS: @(#) $Id: clock.tcl,v 1.21 2005/11/29 22:50:58 kennykb Exp $
#
#----------------------------------------------------------------------
@@ -123,6 +123,7 @@ proc ::tcl::clock::Initialize {} {
{-9223372036854775808 0 0 UTC}
}
set TZData(:UTC) $TZData(:Etc/UTC)
+ set TZData(:localtime) {}
}
InitTZData
@@ -640,6 +641,8 @@ proc ::tcl::clock::Initialize {} {
proc ::tcl::clock::format { args } {
+ variable TZData
+
set format {}
# Check the count of args
@@ -719,15 +722,18 @@ proc ::tcl::clock::format { args } {
# Convert the given time to local time.
- set date [dict create seconds $clockval]
- set date [ConvertUTCToLocal $date[set date {}] $timezone]
+ # Get the data for time changes in the given zone
+ if {[catch {SetupTimeZone $timezone} retval opts]} {
+ dict unset opts -errorinfo
+ return -options $opts $retval
+ }
+
# Extract the fields of the date.
-
- set date [GetJulianDay $date[set date {}]]
- set date [GetGregorianEraYearDay $date[set date {}]]
- set date [GetMonthDay $date[set date {}]]
- set date [GetYearWeekDay $date[set date {}]]
+
+ set date [GetDateFields $clockval \
+ $TZData($timezone) \
+ [mc GREGORIAN_CHANGE_DATE]]
# Format the result
@@ -1233,15 +1239,22 @@ proc ::tcl::clock::scan { args } {
proc ::tcl::clock::FreeScan { string base timezone locale } {
+ variable TZData
+
+ # Get the data for time changes in the given zone
+
+ if {[catch {SetupTimeZone $timezone} retval opts]} {
+ dict unset opts -errorinfo
+ return -options $opts $retval
+ }
+
# Extract year, month and day from the base time for the
# parser to use as defaults
- set date [GetMonthDay \
- [GetGregorianEraYearDay \
- [GetJulianDay \
- [ConvertUTCToLocal \
- [dict create seconds $base] \
- $timezone]]]]
+ set date [GetDateFields \
+ $base \
+ $TZData($timezone) \
+ 2299161]
dict set date secondOfDay [expr { [dict get $date localSeconds]
% 86400 }]
@@ -1294,6 +1307,7 @@ proc ::tcl::clock::FreeScan { string base timezone locale } {
foreach { minEast dstFlag } $parseZone break
set timezone [FormatNumericTimeZone \
[expr { 60 * $minEast + 3600 * $dstFlag }]]
+ SetupTimeZone $timezone
}
dict set date tzName $timezone
@@ -1315,7 +1329,7 @@ proc ::tcl::clock::FreeScan { string base timezone locale } {
+ ( 86400 * wide([dict get $date julianDay]) )
+ [dict get $date secondOfDay] }]
dict set date tzName $timezone
- set date [ConvertLocalToUTC $date[set date {}]]
+ set date [ConvertLocalToUTC $date[set date {}] $TZData($timezone) 2299161]
set seconds [dict get $date seconds]
# Do relative times
@@ -1332,10 +1346,7 @@ proc ::tcl::clock::FreeScan { string base timezone locale } {
if { [llength $parseWeekday] > 0 } {
foreach {dayOrdinal dayOfWeek} $parseWeekday break
- set date2 [GetJulianDay \
- [ConvertUTCToLocal \
- [dict create seconds $seconds] \
- $timezone]]
+ set date2 [GetDateFields $seconds $TZData($timezone) 2299161]
dict set date2 era CE
set jdwkday [WeekdayOnOrBefore $dayOfWeek \
[expr { [dict get $date2 julianDay]
@@ -1352,7 +1363,8 @@ proc ::tcl::clock::FreeScan { string base timezone locale } {
+ ( 86400 * wide([dict get $date2 julianDay]) )
+ [dict get $date secondOfDay] }]
dict set date2 tzName $timezone
- set date2 [ConvertLocalToUTC $date2[set date2 {}]]
+ set date2 [ConvertLocalToUTC $date2[set date2 {}] $TZData($timezone) \
+ 2299161]
set seconds [dict get $date2 seconds]
}
@@ -1844,6 +1856,7 @@ proc ::tcl::clock::ParseClockScanFormat { formatString } {
# Build the procedure
set procBody {}
+ append procBody "variable ::tcl::clock::TZData" \n
append procBody "if \{ !\[ regexp -nocase [list $re] \$string ->"
for { set i 1 } { $i <= $captureCount } { incr i } {
append procBody " " field $i
@@ -1884,8 +1897,16 @@ proc ::tcl::clock::ParseClockScanFormat { formatString } {
if { ![dict exists $fieldSet seconds]
&& ![dict exists $fieldSet starDate] } {
+ if { [dict exists $fieldSet tzName] } {
+ append procBody {
+ set timeZone [dict get $date tzName]
+ }
+ }
append procBody {
- set date [::tcl::clock::ConvertLocalToUTC $date[set date {}]]
+ ::tcl::clock::SetupTimeZone $timeZone
+ set date [::tcl::clock::ConvertLocalToUTC $date[set date {}] \
+ $TZData($timeZone) \
+ [mc GREGORIAN_CHANGE_DATE]]
}
}
@@ -2473,7 +2494,6 @@ proc ::tcl::clock::FormatNumericTimeZone { z } {
}
-
#----------------------------------------------------------------------
#
# FormatStarDate --
@@ -2681,15 +2701,15 @@ proc ::tcl::clock::InterpretTwoDigitYear { date baseTime
#
#----------------------------------------------------------------------
-proc ::tcl::clock::AssignBaseYear { date baseTime timeZone } {
+proc ::tcl::clock::AssignBaseYear { date baseTime timezone } {
+
+ variable TZData
# Find the Julian Day Number corresponding to the base time, and
# find the Gregorian year corresponding to that Julian Day.
- set date2 [dict create seconds $baseTime]
- set date2 [ConvertUTCToLocal $date2[set date2 {}] $timeZone]
- set date2 [GetJulianDay $date2[set date2 {}]]
- set date2 [GetGregorianEraYearDay $date2[set date2 {}]]
+ set date2 [GetDateFields $baseTime $TZData($timezone) \
+ [mc GREGORIAN_CHANGE_DATE]]
# Store the converted year
@@ -2722,15 +2742,15 @@ proc ::tcl::clock::AssignBaseYear { date baseTime timeZone } {
proc ::tcl::clock::AssignBaseIso8601Year { date baseTime timeZone } {
+ variable TZData
+
# Find the Julian Day Number corresponding to the base time
- set date2 [dict create seconds $baseTime]
- set date2 [ConvertUTCToLocal $date2[set date2 {}] $timeZone]
- set date2 [GetJulianDay $date2[set date2 {}]]
+ set date2 [GetDateFields $baseTime $TZData($timeZone) \
+ [mc GREGORIAN_CHANGE_DATE]]
# Calculate the ISO8601 date and transfer the year
- set date2 [GetYearWeekDay $date2[set date2 {}]]
dict set date era CE
dict set date iso8601Year [dict get $date2 iso8601Year]
return $date
@@ -2756,18 +2776,14 @@ proc ::tcl::clock::AssignBaseIso8601Year { date baseTime timeZone } {
#
#----------------------------------------------------------------------
-proc ::tcl::clock::AssignBaseMonth { date baseTime timeZone } {
+proc ::tcl::clock::AssignBaseMonth { date baseTime timezone } {
- # Find the Julian Day Number corresponding to the base time
-
- set date2 [dict create seconds $baseTime]
- set date2 [ConvertUTCToLocal $date2[set date2 {}] $timeZone]
- set date2 [GetJulianDay $date2[set date2 {}]]
+ variable TZData
- # Find the Gregorian year corresponding to that Julian Day
+ # Find the year and month corresponding to the base time
- set date2 [GetGregorianEraYearDay $date2[set date2 {}]]
- set date2 [GetMonthDay $date2[set date2 {}]]
+ set date2 [GetDateFields $baseTime $TZData($timezone) \
+ [mc GREGORIAN_CHANGE_DATE]]
dict set date era [dict get $date2 era]
dict set date year [dict get $date2 year]
dict set date month [dict get $date2 month]
@@ -2797,15 +2813,15 @@ proc ::tcl::clock::AssignBaseMonth { date baseTime timeZone } {
proc ::tcl::clock::AssignBaseWeek { date baseTime timeZone } {
+ variable TZData
+
# Find the Julian Day Number corresponding to the base time
- set date2 [dict create seconds $baseTime]
- set date2 [ConvertUTCToLocal $date2[set date2 {}] $timeZone]
- set date2 [GetJulianDay $date2[set date2 {}]]
+ set date2 [GetDateFields $baseTime $TZData($timeZone) \
+ [mc GREGORIAN_CHANGE_DATE]]
# Calculate the ISO8601 date and transfer the year
- set date2 [GetYearWeekDay $date2[set date2 {}]]
dict set date era CE
dict set date iso8601Year [dict get $date2 iso8601Year]
dict set date iso8601Week [dict get $date2 iso8601Week]
@@ -2833,11 +2849,12 @@ proc ::tcl::clock::AssignBaseWeek { date baseTime timeZone } {
proc ::tcl::clock::AssignBaseJulianDay { date baseTime timeZone } {
+ variable TZData
+
# Find the Julian Day Number corresponding to the base time
- set date2 [dict create seconds $baseTime]
- set date2 [ConvertUTCToLocal $date2[set date2 {}] $timeZone]
- set date2 [GetJulianDay $date2[set date2 {}]]
+ set date2 [GetDateFields $baseTime $TZData($timeZone) \
+ [mc GREGORIAN_CHANGE_DATE]]
dict set date julianDay [dict get $date2 julianDay]
return $date
@@ -2992,249 +3009,6 @@ proc ::tcl::clock::ConvertLegacyTimeZone { tzname } {
#----------------------------------------------------------------------
#
-# ConvertLocalToUTC --
-#
-# Given a time zone and nominal local seconds, compute seconds
-# of UTC time from the Posix epoch.
-#
-# Parameters:
-# date - Dictionary populated with the 'localSeconds' and
-# 'tzName' fields
-#
-# Results:
-# Returns the given dictionary augmented with a 'seconds' field.
-#
-#----------------------------------------------------------------------
-
-proc ::tcl::clock::ConvertLocalToUTC { date } {
-
- variable TZData
-
- set timezone [dict get $date tzName]
- if { $timezone eq ":localtime" } {
-
- # Convert using the mktime function if possible
-
- if { [catch {
- ConvertLocalToUTCViaC [dict get $date localSeconds]
- } result opts] } {
- dict unset opts -errorinfo
- return -options $opts $result
- }
- dict set date seconds $result
- return $date
-
- } else {
-
- # Get the time zone data
-
- if { [catch { SetupTimeZone $timezone } retval opts] } {
- dict unset opts -errorinfo
- return -options $opts $retval
- }
-
- # Initially assume that local == UTC, and locate the last time
- # conversion prior to that time. Get the offset from that,
- # and look up again. If that lookup finds a different offset,
- # continue looking until we find an offset that we found
- # before. The check for "any offset previously found" rather
- # than "the same offset" avoids an endless loop if we try to
- # convert a non-existent time, for example 2:30am during the
- # US spring DST change.
-
- set localseconds [dict get $date localSeconds]
- set utcseconds(0) $localseconds
- set seconds $localseconds
- while { 1 } {
- set i [BSearch $TZData($timezone) $seconds]
- set offset [lindex $TZData($timezone) $i 1]
- if { [info exists utcseconds($offset)] } {
- dict set date seconds $utcseconds($offset)
- return $date
- } else {
- set seconds [expr { $localseconds - $offset }]
- set utcseconds($offset) $seconds
- }
- }
-
- # In the absolute worst case, the loop above can visit each tzdata
- # row only once, so it's guaranteed to terminate.
-
- error "in ConvertLocalToUTC, can't happen"
- }
-
-}
-
-#----------------------------------------------------------------------
-#
-# ConvertLocalToUTCViaC --
-#
-# Given seconds of nominal local time, compute seconds from the
-# Posix epoch.
-#
-# Parameters:
-# localSeconds - Seconds of nominal local time
-#
-# Results:
-# Returns the seconds from the epoch. May throw an error if
-# the time is to large/small to represent, or if 'mktime' is
-# not present in the C library.
-#
-# Side effects:
-# None.
-#
-#----------------------------------------------------------------------
-
-proc ::tcl::clock::ConvertLocalToUTCViaC { localSeconds } {
-
- set date [dict create localSeconds $localSeconds]
- set date [GetJulianDay $date[set date {}]]
- set date [GetGregorianEraYearDay $date[set date {}]]
- set date [GetMonthDay $date[set date {}]]
- set retval \
- [Mktime \
- [dict get $date year] \
- [dict get $date month] \
- [dict get $date dayOfMonth] \
- [expr { $localSeconds / 3600 % 24 }] \
- [expr { $localSeconds / 60 % 60 }] \
- [expr { $localSeconds % 60 }]]
- return $retval
-}
-
-#----------------------------------------------------------------------
-#
-# ConvertUTCToLocal --
-#
-# Given the seconds from the Posix epoch, compute seconds of
-# nominal local time.
-#
-# Parameters:
-# date - Dictionary populated on entry with the 'seconds' field
-#
-# Results:
-# The given dictionary is returned, augmented with 'localSeconds',
-# 'tzOffset', and 'tzName' fields.
-#
-#----------------------------------------------------------------------
-
-proc ::tcl::clock::ConvertUTCToLocal { date timezone } {
-
- variable TZData
-
- # Get the data for time changes in the given zone
-
- if { [catch { SetupTimeZone $timezone } retval opts] } {
- dict unset opts -errorinfo
- return -options $opts $retval
- }
-
- if { $timezone eq {:localtime} } {
-
- # Convert using the localtime function
-
- if { [catch {
- ConvertUTCToLocalViaC $date
- } retval opts] } {
- dict unset opts -errorinfo
- return -options $opts $retval
- }
- return $retval
- }
-
- # Find the most recent transition in the time zone data
-
- set i [BSearch $TZData($timezone) [dict get $date seconds]]
- set row [lindex $TZData($timezone) $i]
- foreach { junk1 offset junk2 name } $row break
-
- # Add appropriate offset to convert Greenwich to local, and return
- # the local time
-
- dict set date localSeconds [expr { [dict get $date seconds] + $offset }]
- dict set date tzOffset $offset
- dict set date tzName $name
-
- return $date
-
-}
-
-#----------------------------------------------------------------------
-#
-# ConvertUTCToLocalViaC --
-#
-# Convert local time using the C localtime function
-#
-# Parameters:
-# date - Dictionary populated on entry with the 'seconds'
-# and 'timeZone' fields.
-#
-# Results:
-# The given dictionary is returned, augmented with 'localSeconds',
-# 'tzOffset', and 'tzName' fields.
-#
-#----------------------------------------------------------------------
-
-proc ::tcl::clock::ConvertUTCToLocalViaC { date } {
-
- # Get y-m-d-h-m-s from the C library
-
- set gmtSeconds [dict get $date seconds]
- set localFields [Localtime $gmtSeconds]
- set date2 [dict create]
- foreach key {
- year month dayOfMonth hour minute second
- } value $localFields {
- dict set date2 $key $value
- }
- dict set date2 era CE
-
- # Convert to Julian Day
-
- set date2 [GetJulianDayFromEraYearMonthDay $date2[set date2 {}]]
-
- # Reconvert to seconds from the epoch in local time.
-
- set localSeconds [expr { ( ( ( wide([dict get $date2 julianDay])
- * 24
- + wide([dict get $date2 hour]) )
- * 60
- + wide([dict get $date2 minute]) )
- * 60
- + wide([dict get $date2 second]) )
- - 210866803200 }]
-
- # Determine the name and offset of the timezone
-
- set diff [expr { $localSeconds - $gmtSeconds }]
- if { $diff <= 0 } {
- set signum -
- set delta [expr { - $diff }]
- } else {
- set signum +
- set delta $diff
- }
- set hh [::format %02d [expr { $delta / 3600 }]]
- set mm [::format %02d [expr { ($delta / 60 )
- % 60 }]]
- set ss [::format %02d [expr { $delta % 60 }]]
-
- set zoneName $signum$hh$mm
- if { $ss ne {00} } {
- append zoneName $ss
- }
-
- # Fix the dictionary
-
- dict set date localSeconds $localSeconds
- dict set date tzOffset $diff
- dict set date tzName $zoneName
- return $date
-
-}
-
-#----------------------------------------------------------------------
-#
# SetupTimeZone --
#
# Given the name or specification of a time zone, sets up
@@ -4101,288 +3875,6 @@ proc ::tcl::clock::GetLocaleEra { date } {
return $date
}
-#----------------------------------------------------------------------
-#
-# GetJulianDay --
-#
-# Given the seconds from the Posix epoch, derives the Julian
-# day number.
-#
-# Parameters:
-# date - Dictionary containing the date fields. On input,
-# populated with a 'localSeconds' field that gives the
-# nominal seconds from the epoch (in the local time zone,
-# rather than UTC).
-#
-# Results:
-# Returns the given dictionary, augmented by a 'julianDay'
-# field that gives the Julian Day Number at noon of the current
-# date.
-#
-#----------------------------------------------------------------------
-
-proc ::tcl::clock::GetJulianDay { date } {
-
- set secs [dict get $date localSeconds]
-
- return [dict set date julianDay \
- [expr { ( $secs + 210866803200 )
- / 86400 }]]
-
-}
-
-#----------------------------------------------------------------------
-#
-# GetGregorianEraYearDay --
-#
-# Given the time from the Posix epoch and the current time zone,
-# develops the era, year, and day of year in the Gregorian calendar.
-#
-# Parameters:
-# date - Dictionary containing the date fields. On input, populated
-# with the 'julianDay' key whose value is the Julian Day Number.
-#
-# Results:
-# Returns the given dictionary with the 'gregorian', 'era',
-# 'year', and 'dayOfYear' populated.
-#
-# Side effects:
-# None.
-#
-#----------------------------------------------------------------------
-
-proc ::tcl::clock::GetGregorianEraYearDay { date } {
-
- set jday [dict get $date julianDay]
-
- set changeover [mc GREGORIAN_CHANGE_DATE]
-
- if { $jday >= $changeover } {
-
- # Gregorian date
-
- dict set date gregorian 1
-
- # Calculate number of days since 1 January, 1 CE
-
- set day [expr { $jday - 1721425 - 1 }]
-
- # Calculate number of 400 year cycles
-
- set year 1
- set n [expr { $day / 146097 }]
- incr year [expr { 400 * $n }]
- set day [expr { $day % 146097 }]
-
- # Calculate number of centuries in the current cycle
-
- set n [expr { $day / 36524 }]
- set day [expr { $day % 36524 }]
- if { $n > 3 } {
- set n 3 ; # 31 December 2000, for instance
- incr day 36524 ; # is last day of 400 year cycle
- }
- incr year [expr { 100 * $n }]
-
- } else {
-
- # Julian date
-
- dict set date gregorian 0
-
- # Calculate days since 0 January, 1 CE Julian
-
- set day [expr { $jday - 1721423 - 1 }]
- set year 1
-
- }
-
- # Calculate number of 4-year cycles in current century (or in
- # the Common Era, if the calendar is Julian)
-
- set n [expr { $day / 1461 }]
- set day [expr { $day % 1461 }]
- incr year [expr { 4 * $n }]
-
- # Calculate number of years in current 4-year cycle
-
- set n [expr { $day / 365 }]
- set day [expr { $day % 365 }]
- if { $n > 3 } {
- set n 3 ;# 31 December in a leap year
- incr day 365
- }
- incr year $n
-
- # Calculate the era
-
- if { $year <= 0 } {
- dict set date year [expr { 1 - $year }]
- dict set date era BCE
- } else {
- dict set date year $year
- dict set date era CE
- }
-
- # Return day of the year
-
- dict set date dayOfYear [expr { $day + 1 }]
-
- return $date
-
-}
-
-#----------------------------------------------------------------------
-#
-# GetMonthDay --
-#
-# Given the ordinal number of the day within the year, determines
-# month and day of month in the Gregorian calendar.
-#
-# Parameters:
-# date - Dictionary containing the date fields. On input, populated
-# with the 'era', 'gregorian', 'year' and 'dayOfYear' fields.
-#
-# Results:
-# Returns the given dictionary with the 'month' and 'dayOfMonth'
-# fields populated.
-#
-# Side effects:
-# None.
-#
-#----------------------------------------------------------------------
-
-proc ::tcl::clock::GetMonthDay { date } {
-
- variable DaysInRomanMonthInCommonYear
- variable DaysInRomanMonthInLeapYear
-
- set day [dict get $date dayOfYear]
- if { [IsGregorianLeapYear $date] } {
- set hath $DaysInRomanMonthInLeapYear
- } else {
- set hath $DaysInRomanMonthInCommonYear
- }
- set month 1
- foreach n $hath {
- if { $day <= $n } {
- break
- }
- incr month
- incr day [expr { -$n }]
- }
- dict set date month $month
- dict set date dayOfMonth $day
-
- return $date
-
-}
-
-#----------------------------------------------------------------------
-#
-# GetYearWeekDay
-#
-# Given a julian day number, fiscal year, fiscal week,
-# and day of week in the ISO8601 calendar.
-#
-# Parameters:
-#
-# date - Dictionary where the 'julianDay' field is populated.
-# daysInFirstWeek - (Optional) Parameter giving the minimum number
-# of days in the first week of a year. Default is 4.
-#
-# Results:
-# Returns the given dictionary with values filled in for the
-# three given keys.
-#
-# Side effects:
-# None.
-#
-# Bugs:
-# Since ISO8601 week numbering is defined only for the Gregorian
-# calendar, dates on the Julian calendar or before the Common
-# Era may yield unexpected results. In particular, the year of
-# the Julian-to-Gregorian change may be up to three weeks short.
-# The era is not managed separately, so if the Common Era begins
-# (or the period Before the Common Era ends) with a partial week,
-# the few days at the beginning or end of the era may show up
-# as incorrectly belonging to the year zero.
-#
-#----------------------------------------------------------------------
-
-proc ::tcl::clock::GetYearWeekDay { date
- { keys { iso8601Year iso8601Week dayOfWeek } } } {
-
- set daysInFirstWeek 4
- set firstDayOfWeek 1
-
- # Determine the calendar year of $j - $daysInFirstWeek + 1.
- # Compute an upper bound of the fiscal year as being one year
- # past the day on which the current week begins. Find the start
- # of that year.
-
- set j [dict get $date julianDay]
- set jd [expr { $j - $daysInFirstWeek + 1 }]
- set date1 [GetGregorianEraYearDay [dict create julianDay $jd]]
- switch -exact -- [dict get $date1 era] {
- BCE {
- dict set date1 fiscalYear [expr { [dict get $date1 year] - 1}]
- }
- CE {
- dict set date1 fiscalYear [expr { [dict get $date1 year] + 1}]
- }
- }
- dict unset date1 year
- dict unset date1 dayOfYear
- dict set date1 weekOfFiscalYear 1
- dict set date1 dayOfWeek $firstDayOfWeek
-
- set date1 [GetJulianDayFromEraYearWeekDay \
- $date1[set date1 {}] \
- $daysInFirstWeek \
- $firstDayOfWeek \
- { fiscalYear weekOfFiscalYear dayOfWeek }]
- set startOfFiscalYear [dict get $date1 julianDay]
-
- # If we guessed high, move one year earlier.
-
- if { $j < $startOfFiscalYear } {
- switch -exact -- [dict get $date1 era] {
- BCE {
- dict incr date1 fiscalYear
- }
- CE {
- dict incr date1 fiscalYear -1
- }
- }
- set date1 [GetJulianDayFromEraYearWeekDay \
- $date1[set date1 {}] \
- $daysInFirstWeek \
- $firstDayOfWeek \
- {fiscalYear weekOfFiscalYear dayOfWeek }]
- set startOfFiscalYear [dict get $date1 julianDay]
- }
-
- # Get the week number and the day within the week
-
- set fiscalYear [dict get $date1 fiscalYear]
- set dayOfFiscalYear [expr { $j - $startOfFiscalYear }]
- set weekOfFiscalYear [expr { ( $dayOfFiscalYear / 7 ) + 1 }]
- set dayOfWeek [expr { ( $dayOfFiscalYear + 1 ) % 7 }]
- if { $dayOfWeek < $firstDayOfWeek } {
- incr dayOfWeek 7
- }
-
- # Store the fiscal year, week, and day in the given slots in the
- # given dictionary.
-
- foreach key $keys \
- value [list $fiscalYear $weekOfFiscalYear $dayOfWeek] {
- dict set date $key $value
- }
-
- return $date
-}
#----------------------------------------------------------------------
#
@@ -4408,6 +3900,9 @@ proc ::tcl::clock::GetYearWeekDay { date
# that gives the Julian Day Number corresponding to the given
# date.
#
+# Bugs:
+# This code needs to be moved to the C layer.
+#
#----------------------------------------------------------------------
proc ::tcl::clock::GetJulianDayFromEraYearWeekDay {
@@ -4467,6 +3962,9 @@ proc ::tcl::clock::GetJulianDayFromEraYearWeekDay {
# Side effects:
# None.
#
+# Bugs:
+# This code needs to be moved to the C layer.
+#
#----------------------------------------------------------------------
proc ::tcl::clock::GetJulianDayFromEraYearMonthDay { date } {
@@ -4552,6 +4050,9 @@ proc ::tcl::clock::GetJulianDayFromEraYearMonthDay { date } {
# Side effects:
# None.
#
+# Bugs:
+# This code needs to be moved to the C layer.
+#
#----------------------------------------------------------------------
proc ::tcl::clock::GetJulianDayFromEraYearDay { date } {
@@ -4609,6 +4110,9 @@ proc ::tcl::clock::GetJulianDayFromEraYearDay { date } {
# Side effects:
# None.
#
+# Bugs:
+# This code needs to be moved to the C layer.
+#
#----------------------------------------------------------------------
proc ::tcl::clock::GetJulianDayFromEraYearMonthWeekDay { date } {
@@ -4859,6 +4363,11 @@ proc ::tcl::clock::add { clockval args } {
}
EnterLocale $locale oldLocale
+
+ if {[catch {SetupTimeZone $timezone} retval opts]} {
+ dict unset opts -errorinfo
+ return -options $opts $retval
+ }
set status [catch {
@@ -4946,15 +4455,13 @@ proc ::tcl::clock::AddMonths { months clockval timezone } {
variable DaysInRomanMonthInCommonYear
variable DaysInRomanMonthInLeapYear
+ variable TZData
+
+ set changeover [mc GREGORIAN_CHANGE_DATE]
# Convert the time to year, month, day, and fraction of day.
- set date [GetMonthDay \
- [GetGregorianEraYearDay \
- [GetJulianDay \
- [ConvertUTCToLocal \
- [dict create seconds $clockval] \
- $timezone]]]]
+ set date [GetDateFields $clockval $TZData($timezone) $changeover]
dict set date secondOfDay [expr { [dict get $date localSeconds]
% 86400 }]
dict set date tzName $timezone
@@ -4988,7 +4495,8 @@ proc ::tcl::clock::AddMonths { months clockval timezone } {
[expr { -210866803200
+ ( 86400 * wide([dict get $date julianDay]) )
+ [dict get $date secondOfDay] }]
- set date [ConvertLocalToUTC $date[set date {}]]
+ set date [ConvertLocalToUTC $date[set date {}] $TZData($timezone) \
+ $changeover]
return [dict get $date seconds]
@@ -5017,12 +4525,13 @@ proc ::tcl::clock::AddMonths { months clockval timezone } {
proc ::tcl::clock::AddDays { days clockval timezone } {
+ variable TZData
+
+ set changeover [mc GREGORIAN_CHANGE_DATE]
+
# Convert the time to Julian Day
- set date [GetJulianDay \
- [ConvertUTCToLocal \
- [dict create seconds $clockval] \
- $timezone]]
+ set date [GetDateFields $clockval $TZData($timezone) $changeover]
dict set date secondOfDay [expr { [dict get $date localSeconds]
% 86400 }]
dict set date tzName $timezone
@@ -5037,7 +4546,8 @@ proc ::tcl::clock::AddDays { days clockval timezone } {
[expr { -210866803200
+ ( 86400 * wide([dict get $date julianDay]) )
+ [dict get $date secondOfDay] }]
- set date [ConvertLocalToUTC $date[set date {}]]
+ set date [ConvertLocalToUTC $date[set date {}] $TZData($timezone) \
+ $changeover]
return [dict get $date seconds]