summaryrefslogtreecommitdiffstats
path: root/generic
diff options
context:
space:
mode:
authorjan.nijtmans <nijtmans@users.sourceforge.net>2024-03-20 08:24:17 (GMT)
committerjan.nijtmans <nijtmans@users.sourceforge.net>2024-03-20 08:24:17 (GMT)
commit72e5911a9e1eed937ac5c5861b546f4803c03d1e (patch)
tree8f47867af5163c86b4bf6fbc6a0a15ed1e06d4a3 /generic
parenta72c1f969cba896ca61001393f0f7b74fb52e194 (diff)
parentba192358693ff4446b9cbd8fda91fb0958ea07e5 (diff)
downloadtcl-72e5911a9e1eed937ac5c5861b546f4803c03d1e.zip
tcl-72e5911a9e1eed937ac5c5861b546f4803c03d1e.tar.gz
tcl-72e5911a9e1eed937ac5c5861b546f4803c03d1e.tar.bz2
Rebase to latest 9.0
Diffstat (limited to 'generic')
-rw-r--r--generic/tclClock.c3227
-rw-r--r--generic/tclClockFmt.c3391
-rw-r--r--generic/tclDate.c1044
-rw-r--r--generic/tclDate.h569
-rw-r--r--generic/tclDictObj.c54
-rw-r--r--generic/tclGetDate.y682
-rw-r--r--generic/tclInt.h1
-rw-r--r--generic/tclStrIdxTree.c530
-rw-r--r--generic/tclStrIdxTree.h155
9 files changed, 8448 insertions, 1205 deletions
diff --git a/generic/tclClock.c b/generic/tclClock.c
index b870f79..6c6ac94 100644
--- a/generic/tclClock.c
+++ b/generic/tclClock.c
@@ -8,6 +8,7 @@
* Copyright © 1991-1995 Karl Lehenbauer & Mark Diekhans.
* Copyright © 1995 Sun Microsystems, Inc.
* Copyright © 2004 Kevin B. Kenny. All rights reserved.
+ * Copyright © 2015 Sergey G. Brester aka sebres. All rights reserved.
*
* See the file "license.terms" for information on usage and redistribution of
* this file, and for a DISCLAIMER OF ALL WARRANTIES.
@@ -15,6 +16,8 @@
#include "tclInt.h"
#include "tclTomMath.h"
+#include "tclStrIdxTree.h"
+#include "tclDate.h"
/*
* Windows has mktime. The configurators do not check.
@@ -25,24 +28,13 @@
#endif
/*
- * Constants
- */
-
-#define JULIAN_DAY_POSIX_EPOCH 2440588
-#define SECONDS_PER_DAY 86400
-#define JULIAN_SEC_POSIX_EPOCH (((Tcl_WideInt) JULIAN_DAY_POSIX_EPOCH) \
- * SECONDS_PER_DAY)
-#define FOUR_CENTURIES 146097 /* days */
-#define JDAY_1_JAN_1_CE_JULIAN 1721424
-#define JDAY_1_JAN_1_CE_GREGORIAN 1721426
-#define ONE_CENTURY_GREGORIAN 36524 /* days */
-#define FOUR_YEARS 1461 /* days */
-#define ONE_YEAR 365 /* days */
-
-/*
* Table of the days in each month, leap and common years
*/
+static const int hath[2][12] = {
+ {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
+ {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
+};
static const int daysInPriorMonths[2][13] = {
{0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365},
{0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366}
@@ -52,70 +44,13 @@ static const int daysInPriorMonths[2][13] = {
* Enumeration of the string literals used in [clock]
*/
-typedef enum ClockLiteral {
- LIT__NIL,
- LIT__DEFAULT_FORMAT,
- LIT_BCE, LIT_C,
- LIT_CANNOT_USE_GMT_AND_TIMEZONE,
- LIT_CE,
- LIT_DAYOFMONTH, LIT_DAYOFWEEK, LIT_DAYOFYEAR,
- LIT_ERA, LIT_GMT, LIT_GREGORIAN,
- LIT_INTEGER_VALUE_TOO_LARGE,
- LIT_ISO8601WEEK, LIT_ISO8601YEAR,
- LIT_JULIANDAY, LIT_LOCALSECONDS,
- LIT_MONTH,
- LIT_SECONDS, LIT_TZNAME, LIT_TZOFFSET,
- LIT_YEAR,
- LIT__END
-} ClockLiteral;
-static const char *const literals[] = {
- "",
- "%a %b %d %H:%M:%S %Z %Y",
- "BCE", "C",
- "cannot use -gmt and -timezone in same call",
- "CE",
- "dayOfMonth", "dayOfWeek", "dayOfYear",
- "era", ":GMT", "gregorian",
- "integer value too large to represent",
- "iso8601Week", "iso8601Year",
- "julianDay", "localSeconds",
- "month",
- "seconds", "tzName", "tzOffset",
- "year"
-};
+CLOCK_LITERAL_ARRAY(Literals);
-/*
- * Structure containing the client data for [clock]
- */
+/* Msgcat literals for exact match (mcKey) */
+CLOCK_LOCALE_LITERAL_ARRAY(MsgCtLiterals, "");
+/* Msgcat index literals prefixed with _IDX_, used for quick dictionary search */
+CLOCK_LOCALE_LITERAL_ARRAY(MsgCtLitIdxs, "_IDX_");
-typedef struct {
- size_t 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 {
- 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 */
- int isBce; /* 1 if BCE */
- int gregorian; /* Flag == 1 if the date is Gregorian */
- int year; /* Year of the era */
- int dayOfYear; /* Day of the year (1 January == 1) */
- int month; /* Month number */
- int dayOfMonth; /* Day of the month */
- int iso8601Year; /* ISO8601 week-based year */
- int iso8601Week; /* ISO8601 week number */
- int dayOfWeek; /* Day of the week */
-} TclDateFields;
static const char *const eras[] = { "CE", "BCE", NULL };
/*
@@ -136,41 +71,56 @@ 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 *, Tcl_Size, Tcl_Obj *const[]);
+ TclDateFields *, Tcl_Size, Tcl_Obj *const[],
+ Tcl_WideInt *rangesVal);
static int ConvertUTCToLocalUsingC(Tcl_Interp *,
TclDateFields *, int);
-static int ConvertLocalToUTC(Tcl_Interp *,
- TclDateFields *, Tcl_Obj *, int);
+static int ConvertLocalToUTC(void *clientData, Tcl_Interp *,
+ TclDateFields *, Tcl_Obj *timezoneObj, int);
static int ConvertLocalToUTCUsingTable(Tcl_Interp *,
- TclDateFields *, Tcl_Size, Tcl_Obj *const[]);
+ TclDateFields *, int, Tcl_Obj *const[],
+ Tcl_WideInt *rangesVal);
static int ConvertLocalToUTCUsingC(Tcl_Interp *,
TclDateFields *, int);
-static Tcl_Obj * LookupLastTransition(Tcl_Interp *, Tcl_WideInt,
- Tcl_Size, Tcl_Obj *const *);
+static int ClockConfigureObjCmd(void *clientData,
+ Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]);
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 Tcl_WideInt WeekdayOnOrBefore(int, Tcl_WideInt);
static Tcl_ObjCmdProc ClockClicksObjCmd;
static Tcl_ObjCmdProc ClockConvertlocaltoutcObjCmd;
+
+static int ClockGetDateFields(void *clientData,
+ Tcl_Interp *interp, TclDateFields *fields,
+ Tcl_Obj *timezoneObj, int changeover);
static Tcl_ObjCmdProc ClockGetdatefieldsObjCmd;
static Tcl_ObjCmdProc ClockGetjuliandayfromerayearmonthdayObjCmd;
static Tcl_ObjCmdProc ClockGetjuliandayfromerayearweekdayObjCmd;
static Tcl_ObjCmdProc ClockGetenvObjCmd;
static Tcl_ObjCmdProc ClockMicrosecondsObjCmd;
static Tcl_ObjCmdProc ClockMillisecondsObjCmd;
-static Tcl_ObjCmdProc ClockParseformatargsObjCmd;
static Tcl_ObjCmdProc ClockSecondsObjCmd;
+static Tcl_ObjCmdProc ClockFormatObjCmd;
+static Tcl_ObjCmdProc ClockScanObjCmd;
+static int ClockScanCommit(
+ DateInfo *info,
+ ClockFmtScnCmdArgs *opts);
+static int ClockFreeScan(
+ DateInfo *info,
+ Tcl_Obj *strObj, ClockFmtScnCmdArgs *opts);
+static int ClockCalcRelTime(
+ DateInfo *info);
+static Tcl_ObjCmdProc ClockAddObjCmd;
+static int ClockValidDate(
+ DateInfo *,
+ ClockFmtScnCmdArgs *, int stage);
static struct tm * ThreadSafeLocalTime(const time_t *);
-static void TzsetIfNecessary(void);
+static size_t TzsetIfNecessary(void);
static void ClockDeleteCmdProc(void *);
+static Tcl_ObjCmdProc ClockSafeCatchCmd;
/*
* Structure containing description of "native" clock commands to create.
*/
@@ -179,22 +129,31 @@ struct ClockCommand {
const char *name; /* The tail of the command name. The full name
* is "::tcl::clock::<name>". When NULL marks
* the end of the table. */
- Tcl_ObjCmdProc *objCmdProc; /* Function that implements the command. This
+ Tcl_ObjCmdProc *objCmdProc; /* Function that implements the command. This
* will always have the ClockClientData sent
* to it, but may well ignore this data. */
+ CompileProc *compileProc; /* The compiler for the command. */
+ void *clientData; /* Any clientData to give the command (if NULL
+ * a reference to ClockClientData will be sent) */
};
static const struct ClockCommand clockCommands[] = {
- {"getenv", ClockGetenvObjCmd},
- {"Oldscan", TclClockOldscanObjCmd},
- {"ConvertLocalToUTC", ClockConvertlocaltoutcObjCmd},
- {"GetDateFields", ClockGetdatefieldsObjCmd},
+ {"add", ClockAddObjCmd, TclCompileBasicMin1ArgCmd, NULL},
+ {"clicks", ClockClicksObjCmd, TclCompileClockClicksCmd, NULL},
+ {"format", ClockFormatObjCmd, TclCompileBasicMin1ArgCmd, NULL},
+ {"getenv", ClockGetenvObjCmd, TclCompileBasicMin1ArgCmd, NULL},
+ {"microseconds", ClockMicrosecondsObjCmd,TclCompileClockReadingCmd, INT2PTR(1)},
+ {"milliseconds", ClockMillisecondsObjCmd,TclCompileClockReadingCmd, INT2PTR(2)},
+ {"scan", ClockScanObjCmd, TclCompileBasicMin1ArgCmd, NULL},
+ {"seconds", ClockSecondsObjCmd, TclCompileClockReadingCmd, INT2PTR(3)},
+ {"ConvertLocalToUTC", ClockConvertlocaltoutcObjCmd, NULL, NULL},
+ {"GetDateFields", ClockGetdatefieldsObjCmd, NULL, NULL},
{"GetJulianDayFromEraYearMonthDay",
- ClockGetjuliandayfromerayearmonthdayObjCmd},
+ ClockGetjuliandayfromerayearmonthdayObjCmd, NULL, NULL},
{"GetJulianDayFromEraYearWeekDay",
- ClockGetjuliandayfromerayearweekdayObjCmd},
- {"ParseFormatArgs", ClockParseformatargsObjCmd},
- {NULL, NULL}
+ ClockGetjuliandayfromerayearweekdayObjCmd, NULL, NULL},
+ {"catch", ClockSafeCatchCmd, TclCompileBasicMin1ArgCmd, NULL},
+ {NULL, NULL, NULL, NULL}
};
/*
@@ -223,22 +182,10 @@ TclClockInit(
char cmdName[50]; /* Buffer large enough to hold the string
*::tcl::clock::GetJulianDayFromEraYearMonthDay
* plus a terminating NUL. */
+ Command *cmdPtr;
ClockClientData *data;
int i;
- /* Structure of the 'clock' ensemble */
-
- static const EnsembleImplMap clockImplMap[] = {
- {"add", NULL, TclCompileBasicMin1ArgCmd, NULL, NULL, 0},
- {"clicks", ClockClicksObjCmd, TclCompileClockClicksCmd, NULL, NULL, 0},
- {"format", NULL, TclCompileBasicMin1ArgCmd, NULL, NULL, 0},
- {"microseconds", ClockMicrosecondsObjCmd, TclCompileClockReadingCmd, NULL, INT2PTR(1), 0},
- {"milliseconds", ClockMillisecondsObjCmd, TclCompileClockReadingCmd, NULL, INT2PTR(2), 0},
- {"scan", NULL, TclCompileBasicMin1ArgCmd, NULL, NULL , 0},
- {"seconds", ClockSecondsObjCmd, TclCompileClockReadingCmd, NULL, INT2PTR(3), 0},
- {NULL, NULL, NULL, NULL, NULL, 0}
- };
-
/*
* Safe interps get [::clock] as alias to a parent, so do not need their
* own copies of the support routines.
@@ -256,27 +203,1197 @@ TclClockInit(
data->refCount = 0;
data->literals = (Tcl_Obj **)Tcl_Alloc(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]);
+ Tcl_InitObjRef(data->literals[i], Tcl_NewStringObj(Literals[i], -1));
}
+ data->mcLiterals = NULL;
+ data->mcLitIdxs = NULL;
+ data->mcDicts = NULL;
+ data->lastTZEpoch = 0;
+ data->currentYearCentury = ClockDefaultYearCentury;
+ data->yearOfCenturySwitch = ClockDefaultCenturySwitch;
+ data->validMinYear = INT_MIN;
+ data->validMaxYear = INT_MAX;
+ /* corresponds max of JDN in sqlite - 9999-12-31 23:59:59 per default */
+ data->maxJDN = 5373484.499999994;
+
+ data->systemTimeZone = NULL;
+ data->systemSetupTZData = NULL;
+ data->gmtSetupTimeZoneUnnorm = NULL;
+ data->gmtSetupTimeZone = NULL;
+ data->gmtSetupTZData = NULL;
+ data->gmtTZName = NULL;
+ data->lastSetupTimeZoneUnnorm = NULL;
+ data->lastSetupTimeZone = NULL;
+ data->lastSetupTZData = NULL;
+ data->prevSetupTimeZoneUnnorm = NULL;
+ data->prevSetupTimeZone = NULL;
+ data->prevSetupTZData = NULL;
+
+ data->defaultLocale = NULL;
+ data->defaultLocaleDict = NULL;
+ data->currentLocale = NULL;
+ data->currentLocaleDict = NULL;
+ data->lastUsedLocaleUnnorm = NULL;
+ data->lastUsedLocale = NULL;
+ data->lastUsedLocaleDict = NULL;
+ data->prevUsedLocaleUnnorm = NULL;
+ data->prevUsedLocale = NULL;
+ data->prevUsedLocaleDict = NULL;
+
+ data->lastBase.timezoneObj = NULL;
+
+ memset(&data->lastTZOffsCache, 0, sizeof(data->lastTZOffsCache));
+
+ data->defFlags = 0;
/*
* Install the commands.
- * TODO - Let Tcl_MakeEnsemble do this?
*/
#define TCL_CLOCK_PREFIX_LEN 14 /* == strlen("::tcl::clock::") */
memcpy(cmdName, "::tcl::clock::", TCL_CLOCK_PREFIX_LEN);
for (clockCmdPtr=clockCommands ; clockCmdPtr->name!=NULL ; clockCmdPtr++) {
+ void *clientData;
+
strcpy(cmdName + TCL_CLOCK_PREFIX_LEN, clockCmdPtr->name);
- data->refCount++;
- Tcl_CreateObjCommand(interp, cmdName, clockCmdPtr->objCmdProc, data,
- ClockDeleteCmdProc);
+ if (!(clientData = clockCmdPtr->clientData)) {
+ clientData = data;
+ data->refCount++;
+ }
+ cmdPtr = (Command *)Tcl_CreateObjCommand(interp, cmdName,
+ clockCmdPtr->objCmdProc, clientData,
+ clockCmdPtr->clientData ? NULL : ClockDeleteCmdProc);
+ cmdPtr->compileProc = clockCmdPtr->compileProc ?
+ clockCmdPtr->compileProc : TclCompileBasicMin0ArgCmd;
+ }
+ cmdPtr = (Command *)Tcl_CreateObjCommand(interp,
+ "::tcl::unsupported::clock::configure",
+ ClockConfigureObjCmd, data, NULL);
+ data->refCount++;
+ cmdPtr->compileProc = TclCompileBasicMin0ArgCmd;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * ClockConfigureClear --
+ *
+ * Clean up cached resp. run-time storages used in clock commands.
+ *
+ * Shared usage for clean-up (ClockDeleteCmdProc) and "configure -clear".
+ *
+ * Results:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+ClockConfigureClear(
+ ClockClientData *data)
+{
+ ClockFrmScnClearCaches();
+
+ data->lastTZEpoch = 0;
+ Tcl_UnsetObjRef(data->systemTimeZone);
+ Tcl_UnsetObjRef(data->systemSetupTZData);
+ Tcl_UnsetObjRef(data->gmtSetupTimeZoneUnnorm);
+ Tcl_UnsetObjRef(data->gmtSetupTimeZone);
+ Tcl_UnsetObjRef(data->gmtSetupTZData);
+ Tcl_UnsetObjRef(data->gmtTZName);
+ Tcl_UnsetObjRef(data->lastSetupTimeZoneUnnorm);
+ Tcl_UnsetObjRef(data->lastSetupTimeZone);
+ Tcl_UnsetObjRef(data->lastSetupTZData);
+ Tcl_UnsetObjRef(data->prevSetupTimeZoneUnnorm);
+ Tcl_UnsetObjRef(data->prevSetupTimeZone);
+ Tcl_UnsetObjRef(data->prevSetupTZData);
+
+ Tcl_UnsetObjRef(data->defaultLocale);
+ data->defaultLocaleDict = NULL;
+ Tcl_UnsetObjRef(data->currentLocale);
+ data->currentLocaleDict = NULL;
+ Tcl_UnsetObjRef(data->lastUsedLocaleUnnorm);
+ Tcl_UnsetObjRef(data->lastUsedLocale);
+ data->lastUsedLocaleDict = NULL;
+ Tcl_UnsetObjRef(data->prevUsedLocaleUnnorm);
+ Tcl_UnsetObjRef(data->prevUsedLocale);
+ data->prevUsedLocaleDict = NULL;
+
+ Tcl_UnsetObjRef(data->lastBase.timezoneObj);
+
+ Tcl_UnsetObjRef(data->lastTZOffsCache[0].timezoneObj);
+ Tcl_UnsetObjRef(data->lastTZOffsCache[0].tzName);
+ Tcl_UnsetObjRef(data->lastTZOffsCache[1].timezoneObj);
+ Tcl_UnsetObjRef(data->lastTZOffsCache[1].tzName);
+
+ Tcl_UnsetObjRef(data->mcDicts);
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * ClockDeleteCmdProc --
+ *
+ * Remove a reference to the clock client data, and clean up memory
+ * when it's all gone.
+ *
+ * Results:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+static void
+ClockDeleteCmdProc(
+ void *clientData) /* Opaque pointer to the client data */
+{
+ ClockClientData *data = (ClockClientData *)clientData;
+ int i;
+
+ if (data->refCount-- <= 1) {
+ for (i = 0; i < LIT__END; ++i) {
+ Tcl_DecrRefCount(data->literals[i]);
+ }
+ if (data->mcLiterals != NULL) {
+ for (i = 0; i < MCLIT__END; ++i) {
+ Tcl_DecrRefCount(data->mcLiterals[i]);
+ }
+ data->mcLiterals = NULL;
+ }
+ if (data->mcLitIdxs != NULL) {
+ for (i = 0; i < MCLIT__END; ++i) {
+ Tcl_DecrRefCount(data->mcLitIdxs[i]);
+ }
+ data->mcLitIdxs = NULL;
+ }
+
+ ClockConfigureClear(data);
+
+ Tcl_Free(data->literals);
+ Tcl_Free(data);
+ }
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * SavePrevTimezoneObj --
+ *
+ * Used to store previously used/cached time zone (makes it reusable).
+ *
+ * This enables faster switch between time zones (e. g. to convert from one to another).
+ *
+ * Results:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static inline void
+SavePrevTimezoneObj(
+ ClockClientData *dataPtr) /* Client data containing literal pool */
+{
+ Tcl_Obj *timezoneObj = dataPtr->lastSetupTimeZone;
+ if (timezoneObj && timezoneObj != dataPtr->prevSetupTimeZone) {
+ Tcl_SetObjRef(dataPtr->prevSetupTimeZoneUnnorm, dataPtr->lastSetupTimeZoneUnnorm);
+ Tcl_SetObjRef(dataPtr->prevSetupTimeZone, timezoneObj);
+ Tcl_SetObjRef(dataPtr->prevSetupTZData, dataPtr->lastSetupTZData);
+ }
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * NormTimezoneObj --
+ *
+ * Normalizes the timezone object (used for caching puposes).
+ *
+ * If already cached time zone could be found, returns this
+ * object (last setup or last used, system (current) or gmt).
+ *
+ * Results:
+ * Normalized tcl object pointer.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static Tcl_Obj *
+NormTimezoneObj(
+ ClockClientData *dataPtr, /* Client data containing literal pool */
+ Tcl_Obj *timezoneObj, /* Name of zone to find */
+ int *loaded) /* Used to recognized TZ was loaded */
+{
+ const char *tz;
+
+ *loaded = 1;
+ if ( timezoneObj == dataPtr->lastSetupTimeZoneUnnorm
+ && dataPtr->lastSetupTimeZone != NULL
+ ) {
+ return dataPtr->lastSetupTimeZone;
+ }
+ if ( timezoneObj == dataPtr->prevSetupTimeZoneUnnorm
+ && dataPtr->prevSetupTimeZone != NULL
+ ) {
+ return dataPtr->prevSetupTimeZone;
+ }
+ if (timezoneObj == dataPtr->gmtSetupTimeZoneUnnorm
+ && dataPtr->gmtSetupTimeZone != NULL
+ ) {
+ return dataPtr->literals[LIT_GMT];
+ }
+ if ( timezoneObj == dataPtr->lastSetupTimeZone
+ || timezoneObj == dataPtr->prevSetupTimeZone
+ || timezoneObj == dataPtr->gmtSetupTimeZone
+ || timezoneObj == dataPtr->systemTimeZone
+ ) {
+ return timezoneObj;
+ }
+
+ tz = TclGetString(timezoneObj);
+ if (dataPtr->lastSetupTimeZone != NULL &&
+ strcmp(tz, TclGetString(dataPtr->lastSetupTimeZone)) == 0
+ ) {
+ Tcl_SetObjRef(dataPtr->lastSetupTimeZoneUnnorm, timezoneObj);
+ return dataPtr->lastSetupTimeZone;
+ }
+ if (dataPtr->prevSetupTimeZone != NULL &&
+ strcmp(tz, TclGetString(dataPtr->prevSetupTimeZone)) == 0
+ ) {
+ Tcl_SetObjRef(dataPtr->prevSetupTimeZoneUnnorm, timezoneObj);
+ return dataPtr->prevSetupTimeZone;
+ }
+ if (dataPtr->systemTimeZone != NULL &&
+ strcmp(tz, TclGetString(dataPtr->systemTimeZone)) == 0
+ ) {
+ return dataPtr->systemTimeZone;
+ }
+ if (strcmp(tz, Literals[LIT_GMT]) == 0) {
+ Tcl_SetObjRef(dataPtr->gmtSetupTimeZoneUnnorm, timezoneObj);
+ if (dataPtr->gmtSetupTimeZone == NULL) {
+ *loaded = 0;
+ }
+ return dataPtr->literals[LIT_GMT];
+ }
+ /* unknown/unloaded tz - recache/revalidate later as last-setup if needed */
+ *loaded = 0;
+ return timezoneObj;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * ClockGetSystemLocale --
+ *
+ * Returns system locale.
+ *
+ * Executes ::tcl::clock::GetSystemLocale in given interpreter.
+ *
+ * Results:
+ * Returns system locale tcl object.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static inline Tcl_Obj *
+ClockGetSystemLocale(
+ ClockClientData *dataPtr, /* Opaque pointer to literal pool, etc. */
+ Tcl_Interp *interp) /* Tcl interpreter */
+{
+ if (Tcl_EvalObjv(interp, 1, &dataPtr->literals[LIT_GETSYSTEMLOCALE], 0) != TCL_OK) {
+ return NULL;
+ }
+
+ return Tcl_GetObjResult(interp);
+}
+/*
+ *----------------------------------------------------------------------
+ *
+ * ClockGetCurrentLocale --
+ *
+ * Returns current locale.
+ *
+ * Executes ::tcl::clock::mclocale in given interpreter.
+ *
+ * Results:
+ * Returns current locale tcl object.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static inline Tcl_Obj *
+ClockGetCurrentLocale(
+ ClockClientData *dataPtr, /* Client data containing literal pool */
+ Tcl_Interp *interp) /* Tcl interpreter */
+{
+ if (Tcl_EvalObjv(interp, 1, &dataPtr->literals[LIT_GETCURRENTLOCALE], 0) != TCL_OK) {
+ return NULL;
+ }
+
+ Tcl_SetObjRef(dataPtr->currentLocale, Tcl_GetObjResult(interp));
+ dataPtr->currentLocaleDict = NULL;
+ Tcl_ResetResult(interp);
+
+ return dataPtr->currentLocale;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * SavePrevLocaleObj --
+ *
+ * Used to store previously used/cached locale (makes it reusable).
+ *
+ * This enables faster switch between locales (e. g. to convert from one to another).
+ *
+ * Results:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static inline void
+SavePrevLocaleObj(
+ ClockClientData *dataPtr) /* Client data containing literal pool */
+{
+ Tcl_Obj *localeObj = dataPtr->lastUsedLocale;
+ if (localeObj && localeObj != dataPtr->prevUsedLocale) {
+ Tcl_SetObjRef(dataPtr->prevUsedLocaleUnnorm, dataPtr->lastUsedLocaleUnnorm);
+ Tcl_SetObjRef(dataPtr->prevUsedLocale, localeObj);
+ /* mcDicts owns reference to dict */
+ dataPtr->prevUsedLocaleDict = dataPtr->lastUsedLocaleDict;
+ }
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * NormLocaleObj --
+ *
+ * Normalizes the locale object (used for caching puposes).
+ *
+ * If already cached locale could be found, returns this
+ * object (current, system (OS) or last used locales).
+ *
+ * Results:
+ * Normalized tcl object pointer.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static Tcl_Obj *
+NormLocaleObj(
+ ClockClientData *dataPtr, /* Client data containing literal pool */
+ Tcl_Interp *interp, /* Tcl interpreter */
+ Tcl_Obj *localeObj,
+ Tcl_Obj **mcDictObj)
+{
+ const char *loc, *loc2;
+ if ( localeObj == NULL
+ || localeObj == dataPtr->literals[LIT_C]
+ || localeObj == dataPtr->defaultLocale
+ ) {
+ *mcDictObj = dataPtr->defaultLocaleDict;
+ return dataPtr->defaultLocale ?
+ dataPtr->defaultLocale : dataPtr->literals[LIT_C];
+ }
+ if ( localeObj == dataPtr->currentLocale
+ || localeObj == dataPtr->literals[LIT_CURRENT]
+ ) {
+ if (dataPtr->currentLocale == NULL) {
+ ClockGetCurrentLocale(dataPtr, interp);
+ }
+ *mcDictObj = dataPtr->currentLocaleDict;
+ return dataPtr->currentLocale;
+ }
+ if ( localeObj == dataPtr->lastUsedLocale
+ || localeObj == dataPtr->lastUsedLocaleUnnorm
+ ) {
+ *mcDictObj = dataPtr->lastUsedLocaleDict;
+ return dataPtr->lastUsedLocale;
+ }
+ if ( localeObj == dataPtr->prevUsedLocale
+ || localeObj == dataPtr->prevUsedLocaleUnnorm
+ ) {
+ *mcDictObj = dataPtr->prevUsedLocaleDict;
+ return dataPtr->prevUsedLocale;
+ }
+
+ loc = TclGetString(localeObj);
+ if ( dataPtr->currentLocale != NULL
+ && ( localeObj == dataPtr->currentLocale
+ || (localeObj->length == dataPtr->currentLocale->length
+ && strcasecmp(loc, TclGetString(dataPtr->currentLocale)) == 0
+ )
+ )
+ ) {
+ *mcDictObj = dataPtr->currentLocaleDict;
+ return dataPtr->currentLocale;
+ }
+ if ( dataPtr->lastUsedLocale != NULL
+ && ( localeObj == dataPtr->lastUsedLocale
+ || (localeObj->length == dataPtr->lastUsedLocale->length
+ && strcasecmp(loc, TclGetString(dataPtr->lastUsedLocale)) == 0
+ )
+ )
+ ) {
+ *mcDictObj = dataPtr->lastUsedLocaleDict;
+ Tcl_SetObjRef(dataPtr->lastUsedLocaleUnnorm, localeObj);
+ return dataPtr->lastUsedLocale;
+ }
+ if ( dataPtr->prevUsedLocale != NULL
+ && ( localeObj == dataPtr->prevUsedLocale
+ || (localeObj->length == dataPtr->prevUsedLocale->length
+ && strcasecmp(loc, TclGetString(dataPtr->prevUsedLocale)) == 0
+ )
+ )
+ ) {
+ *mcDictObj = dataPtr->prevUsedLocaleDict;
+ Tcl_SetObjRef(dataPtr->prevUsedLocaleUnnorm, localeObj);
+ return dataPtr->prevUsedLocale;
+ }
+ if (
+ (localeObj->length == 1 /* C */
+ && strcasecmp(loc, Literals[LIT_C]) == 0)
+ || (dataPtr->defaultLocale && (loc2 = TclGetString(dataPtr->defaultLocale))
+ && localeObj->length == dataPtr->defaultLocale->length
+ && strcasecmp(loc, loc2) == 0)
+ ) {
+ *mcDictObj = dataPtr->defaultLocaleDict;
+ return dataPtr->defaultLocale ?
+ dataPtr->defaultLocale : dataPtr->literals[LIT_C];
+ }
+ if ( localeObj->length == 7 /* current */
+ && strcasecmp(loc, Literals[LIT_CURRENT]) == 0
+ ) {
+ if (dataPtr->currentLocale == NULL) {
+ ClockGetCurrentLocale(dataPtr, interp);
+ }
+ *mcDictObj = dataPtr->currentLocaleDict;
+ return dataPtr->currentLocale;
+ }
+ if (
+ (localeObj->length == 6 /* system */
+ && strcasecmp(loc, Literals[LIT_SYSTEM]) == 0)
+ ) {
+ SavePrevLocaleObj(dataPtr);
+ Tcl_SetObjRef(dataPtr->lastUsedLocaleUnnorm, localeObj);
+ localeObj = ClockGetSystemLocale(dataPtr, interp);
+ Tcl_SetObjRef(dataPtr->lastUsedLocale, localeObj);
+ *mcDictObj = NULL;
+ return localeObj;
+ }
+ *mcDictObj = NULL;
+ return localeObj;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * ClockMCDict --
+ *
+ * Retrieves a localized storage dictionary object for the given
+ * locale object.
+ *
+ * This corresponds with call `::tcl::clock::mcget locale`.
+ * Cached representation stored in options (for further access).
+ *
+ * Results:
+ * Tcl-object contains smart reference to msgcat dictionary.
+ *
+ *----------------------------------------------------------------------
+ */
+
+Tcl_Obj *
+ClockMCDict(ClockFmtScnCmdArgs *opts)
+{
+ ClockClientData *dataPtr = (ClockClientData *)opts->clientData;
+
+ /* if dict not yet retrieved */
+ if (opts->mcDictObj == NULL) {
+
+ /* if locale was not yet used */
+ if ( !(opts->flags & CLF_LOCALE_USED) ) {
+
+ opts->localeObj = NormLocaleObj((ClockClientData *)opts->clientData, opts->interp,
+ opts->localeObj, &opts->mcDictObj);
+
+ if (opts->localeObj == NULL) {
+ Tcl_SetObjResult(opts->interp,
+ Tcl_NewStringObj("locale not specified and no default locale set", -1));
+ Tcl_SetErrorCode(opts->interp, "CLOCK", "badOption", (char *)NULL);
+ return NULL;
+ }
+ opts->flags |= CLF_LOCALE_USED;
+
+ /* check locale literals already available (on demand creation) */
+ if (dataPtr->mcLiterals == NULL) {
+ int i;
+ dataPtr->mcLiterals = (Tcl_Obj **)Tcl_Alloc(MCLIT__END * sizeof(Tcl_Obj*));
+ for (i = 0; i < MCLIT__END; ++i) {
+ Tcl_InitObjRef(dataPtr->mcLiterals[i],
+ Tcl_NewStringObj(MsgCtLiterals[i], -1));
+ }
+ }
+ }
+
+ /* check or obtain mcDictObj (be sure it's modifiable) */
+ if (opts->mcDictObj == NULL || opts->mcDictObj->refCount > 1) {
+ int ref = 1;
+
+ /* first try to find locale catalog dict */
+ if (dataPtr->mcDicts == NULL) {
+ Tcl_SetObjRef(dataPtr->mcDicts, Tcl_NewDictObj());
+ }
+ Tcl_DictObjGet(NULL, dataPtr->mcDicts,
+ opts->localeObj, &opts->mcDictObj);
+
+ if (opts->mcDictObj == NULL) {
+ /* get msgcat dictionary - ::tcl::clock::mcget locale */
+ Tcl_Obj *callargs[2];
+
+ callargs[0] = dataPtr->literals[LIT_MCGET];
+ callargs[1] = opts->localeObj;
+
+ if (Tcl_EvalObjv(opts->interp, 2, callargs, 0) != TCL_OK) {
+ return NULL;
+ }
+
+ opts->mcDictObj = Tcl_GetObjResult(opts->interp);
+ Tcl_ResetResult(opts->interp);
+ ref = 0; /* new object is not yet referenced */
+ }
+
+ /* be sure that object reference doesn't increase (dict changeable) */
+ if (opts->mcDictObj->refCount > ref) {
+ /* smart reference (shared dict as object with no ref-counter) */
+ opts->mcDictObj = TclDictObjSmartRef(opts->interp,
+ opts->mcDictObj);
+ }
+
+ /* create exactly one reference to catalog / make it searchable for future */
+ Tcl_DictObjPut(NULL, dataPtr->mcDicts, opts->localeObj,
+ opts->mcDictObj);
+
+ if ( opts->localeObj == dataPtr->literals[LIT_C]
+ || opts->localeObj == dataPtr->defaultLocale
+ ) {
+ dataPtr->defaultLocaleDict = opts->mcDictObj;
+ }
+ if ( opts->localeObj == dataPtr->currentLocale ) {
+ dataPtr->currentLocaleDict = opts->mcDictObj;
+ } else if ( opts->localeObj == dataPtr->lastUsedLocale ) {
+ dataPtr->lastUsedLocaleDict = opts->mcDictObj;
+ } else {
+ SavePrevLocaleObj(dataPtr);
+ Tcl_SetObjRef(dataPtr->lastUsedLocale, opts->localeObj);
+ Tcl_UnsetObjRef(dataPtr->lastUsedLocaleUnnorm);
+ dataPtr->lastUsedLocaleDict = opts->mcDictObj;
+ }
+ }
+ }
+
+ return opts->mcDictObj;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * ClockMCGet --
+ *
+ * Retrieves a msgcat value for the given literal integer mcKey
+ * from localized storage (corresponding given locale object)
+ * by mcLiterals[mcKey] (e. g. MONTHS_FULL).
+ *
+ * Results:
+ * Tcl-object contains localized value.
+ *
+ *----------------------------------------------------------------------
+ */
+
+Tcl_Obj *
+ClockMCGet(
+ ClockFmtScnCmdArgs *opts,
+ int mcKey)
+{
+ ClockClientData *dataPtr = (ClockClientData *)opts->clientData;
+
+ Tcl_Obj *valObj = NULL;
+
+ if (opts->mcDictObj == NULL) {
+ ClockMCDict(opts);
+ if (opts->mcDictObj == NULL)
+ return NULL;
+ }
+
+ Tcl_DictObjGet(opts->interp, opts->mcDictObj,
+ dataPtr->mcLiterals[mcKey], &valObj);
+
+ return valObj; /* or NULL in obscure case if Tcl_DictObjGet failed */
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * ClockMCGetIdx --
+ *
+ * Retrieves an indexed msgcat value for the given literal integer mcKey
+ * from localized storage (corresponding given locale object)
+ * by mcLitIdxs[mcKey] (e. g. _IDX_MONTHS_FULL).
+ *
+ * Results:
+ * Tcl-object contains localized indexed value.
+ *
+ *----------------------------------------------------------------------
+ */
+
+MODULE_SCOPE Tcl_Obj *
+ClockMCGetIdx(
+ ClockFmtScnCmdArgs *opts,
+ int mcKey)
+{
+ ClockClientData *dataPtr = (ClockClientData *)opts->clientData;
+
+ Tcl_Obj *valObj = NULL;
+
+ if (opts->mcDictObj == NULL) {
+ ClockMCDict(opts);
+ if (opts->mcDictObj == NULL)
+ return NULL;
+ }
+
+ /* try to get indices object */
+ if (dataPtr->mcLitIdxs == NULL) {
+ return NULL;
}
- /* Make the clock ensemble */
+ if (Tcl_DictObjGet(NULL, opts->mcDictObj,
+ dataPtr->mcLitIdxs[mcKey], &valObj) != TCL_OK
+ ) {
+ return NULL;
+ }
- TclMakeEnsemble(interp, "clock", clockImplMap);
+ return valObj;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * ClockMCSetIdx --
+ *
+ * Sets an indexed msgcat value for the given literal integer mcKey
+ * in localized storage (corresponding given locale object)
+ * by mcLitIdxs[mcKey] (e. g. _IDX_MONTHS_FULL).
+ *
+ * Results:
+ * Returns a standard Tcl result.
+ *
+ *----------------------------------------------------------------------
+ */
+
+int
+ClockMCSetIdx(
+ ClockFmtScnCmdArgs *opts,
+ int mcKey, Tcl_Obj *valObj)
+{
+ ClockClientData *dataPtr = (ClockClientData *)opts->clientData;
+
+ if (opts->mcDictObj == NULL) {
+ ClockMCDict(opts);
+ if (opts->mcDictObj == NULL)
+ return TCL_ERROR;
+ }
+
+ /* if literal storage for indices not yet created */
+ if (dataPtr->mcLitIdxs == NULL) {
+ int i;
+ dataPtr->mcLitIdxs = (Tcl_Obj **)Tcl_Alloc(MCLIT__END * sizeof(Tcl_Obj*));
+ for (i = 0; i < MCLIT__END; ++i) {
+ Tcl_InitObjRef(dataPtr->mcLitIdxs[i],
+ Tcl_NewStringObj(MsgCtLitIdxs[i], -1));
+ }
+ }
+
+ return Tcl_DictObjPut(opts->interp, opts->mcDictObj,
+ dataPtr->mcLitIdxs[mcKey], valObj);
+}
+
+static void
+TimezoneLoaded(
+ ClockClientData *dataPtr,
+ Tcl_Obj *timezoneObj, /* Name of zone was loaded */
+ Tcl_Obj *tzUnnormObj) /* Name of zone was loaded */
+{
+ /* don't overwrite last-setup with GMT (special case) */
+ if (timezoneObj == dataPtr->literals[LIT_GMT]) {
+ /* mark GMT zone loaded */
+ if (dataPtr->gmtSetupTimeZone == NULL) {
+ Tcl_SetObjRef(dataPtr->gmtSetupTimeZone,
+ dataPtr->literals[LIT_GMT]);
+ }
+ Tcl_SetObjRef(dataPtr->gmtSetupTimeZoneUnnorm, tzUnnormObj);
+ return;
+ }
+
+ /* last setup zone loaded */
+ if (dataPtr->lastSetupTimeZone != timezoneObj) {
+ SavePrevTimezoneObj(dataPtr);
+ Tcl_SetObjRef(dataPtr->lastSetupTimeZone, timezoneObj);
+ Tcl_UnsetObjRef(dataPtr->lastSetupTZData);
+ }
+ Tcl_SetObjRef(dataPtr->lastSetupTimeZoneUnnorm, tzUnnormObj);
+}
+/*
+ *----------------------------------------------------------------------
+ *
+ * ClockConfigureObjCmd --
+ *
+ * This function is invoked to process the Tcl "::clock::configure" (internal) command.
+ *
+ * Usage:
+ * ::tcl::unsupported::clock::configure ?-option ?value??
+ *
+ * Results:
+ * Returns a standard Tcl result.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static int
+ClockConfigureObjCmd(
+ void *clientData, /* Client data containing literal pool */
+ Tcl_Interp *interp, /* Tcl interpreter */
+ int objc, /* Parameter count */
+ Tcl_Obj *const objv[]) /* Parameter vector */
+{
+ ClockClientData *dataPtr = (ClockClientData *)clientData;
+
+ static const char *const options[] = {
+ "-system-tz", "-setup-tz", "-default-locale", "-current-locale",
+ "-clear",
+ "-year-century", "-century-switch",
+ "-min-year", "-max-year", "-max-jdn", "-validate",
+ "-init-complete",
+ NULL
+ };
+ enum optionInd {
+ CLOCK_SYSTEM_TZ, CLOCK_SETUP_TZ, CLOCK_DEFAULT_LOCALE, CLOCK_CURRENT_LOCALE,
+ CLOCK_CLEAR_CACHE,
+ CLOCK_YEAR_CENTURY, CLOCK_CENTURY_SWITCH,
+ CLOCK_MIN_YEAR, CLOCK_MAX_YEAR, CLOCK_MAX_JDN, CLOCK_VALIDATE,
+ CLOCK_INIT_COMPLETE
+ };
+ int optionIndex; /* Index of an option. */
+ int i;
+
+ for (i = 1; i < objc; i++) {
+ if (Tcl_GetIndexFromObj(interp, objv[i++], options,
+ "option", 0, &optionIndex) != TCL_OK) {
+ Tcl_SetErrorCode(interp, "CLOCK", "badOption",
+ Tcl_GetString(objv[i-1]), (char *)NULL);
+ return TCL_ERROR;
+ }
+ switch (optionIndex) {
+ case CLOCK_SYSTEM_TZ:
+ if (1) {
+ /* validate current tz-epoch */
+ size_t lastTZEpoch = TzsetIfNecessary();
+ if (i < objc) {
+ if (dataPtr->systemTimeZone != objv[i]) {
+ Tcl_SetObjRef(dataPtr->systemTimeZone, objv[i]);
+ Tcl_UnsetObjRef(dataPtr->systemSetupTZData);
+ }
+ dataPtr->lastTZEpoch = lastTZEpoch;
+ }
+ if (i+1 >= objc && dataPtr->systemTimeZone != NULL
+ && dataPtr->lastTZEpoch == lastTZEpoch) {
+ Tcl_SetObjResult(interp, dataPtr->systemTimeZone);
+ }
+ }
+ break;
+ case CLOCK_SETUP_TZ:
+ if (i < objc) {
+ int loaded;
+ Tcl_Obj *timezoneObj = NormTimezoneObj(dataPtr, objv[i], &loaded);
+ if (!loaded) {
+ TimezoneLoaded(dataPtr, timezoneObj, objv[i]);
+ }
+ Tcl_SetObjResult(interp, timezoneObj);
+ }
+ else
+ if (i+1 >= objc && dataPtr->lastSetupTimeZone != NULL) {
+ Tcl_SetObjResult(interp, dataPtr->lastSetupTimeZone);
+ }
+ break;
+ case CLOCK_DEFAULT_LOCALE:
+ if (i < objc) {
+ if (dataPtr->defaultLocale != objv[i]) {
+ Tcl_SetObjRef(dataPtr->defaultLocale, objv[i]);
+ dataPtr->defaultLocaleDict = NULL;
+ }
+ }
+ if (i+1 >= objc) {
+ Tcl_SetObjResult(interp, dataPtr->defaultLocale ?
+ dataPtr->defaultLocale : dataPtr->literals[LIT_C]);
+ }
+ break;
+ case CLOCK_CURRENT_LOCALE:
+ if (i < objc) {
+ if (dataPtr->currentLocale != objv[i]) {
+ Tcl_SetObjRef(dataPtr->currentLocale, objv[i]);
+ dataPtr->currentLocaleDict = NULL;
+ }
+ }
+ if (i+1 >= objc && dataPtr->currentLocale != NULL) {
+ Tcl_SetObjResult(interp, dataPtr->currentLocale);
+ }
+ break;
+ case CLOCK_YEAR_CENTURY:
+ if (i < objc) {
+ int year;
+ if (TclGetIntFromObj(interp, objv[i], &year) != TCL_OK) {
+ return TCL_ERROR;
+ }
+ dataPtr->currentYearCentury = year;
+ if (i+1 >= objc) {
+ Tcl_SetObjResult(interp, objv[i]);
+ }
+ continue;
+ }
+ if (i+1 >= objc) {
+ Tcl_SetObjResult(interp,
+ Tcl_NewWideIntObj(dataPtr->currentYearCentury));
+ }
+ break;
+ case CLOCK_CENTURY_SWITCH:
+ if (i < objc) {
+ int year;
+ if (TclGetIntFromObj(interp, objv[i], &year) != TCL_OK) {
+ return TCL_ERROR;
+ }
+ dataPtr->yearOfCenturySwitch = year;
+ Tcl_SetObjResult(interp, objv[i]);
+ continue;
+ }
+ if (i+1 >= objc) {
+ Tcl_SetObjResult(interp,
+ Tcl_NewWideIntObj(dataPtr->yearOfCenturySwitch));
+ }
+ break;
+ case CLOCK_MIN_YEAR:
+ if (i < objc) {
+ int year;
+ if (TclGetIntFromObj(interp, objv[i], &year) != TCL_OK) {
+ return TCL_ERROR;
+ }
+ dataPtr->validMinYear = year;
+ Tcl_SetObjResult(interp, objv[i]);
+ continue;
+ }
+ if (i+1 >= objc) {
+ Tcl_SetObjResult(interp,
+ Tcl_NewWideIntObj(dataPtr->validMinYear));
+ }
+ break;
+ case CLOCK_MAX_YEAR:
+ if (i < objc) {
+ int year;
+ if (TclGetIntFromObj(interp, objv[i], &year) != TCL_OK) {
+ return TCL_ERROR;
+ }
+ dataPtr->validMaxYear = year;
+ Tcl_SetObjResult(interp, objv[i]);
+ continue;
+ }
+ if (i+1 >= objc) {
+ Tcl_SetObjResult(interp,
+ Tcl_NewWideIntObj(dataPtr->validMaxYear));
+ }
+ break;
+ case CLOCK_MAX_JDN:
+ if (i < objc) {
+ double jd;
+ if (Tcl_GetDoubleFromObj(interp, objv[i], &jd) != TCL_OK) {
+ return TCL_ERROR;
+ }
+ dataPtr->maxJDN = jd;
+ Tcl_SetObjResult(interp, objv[i]);
+ continue;
+ }
+ if (i+1 >= objc) {
+ Tcl_SetObjResult(interp,
+ Tcl_NewDoubleObj(dataPtr->maxJDN));
+ }
+ break;
+ case CLOCK_VALIDATE:
+ if (i < objc) {
+ int val;
+ if (Tcl_GetBooleanFromObj(interp, objv[i], &val) != TCL_OK) {
+ return TCL_ERROR;
+ }
+ if (val) {
+ dataPtr->defFlags |= CLF_VALIDATE;
+ } else {
+ dataPtr->defFlags &= ~CLF_VALIDATE;
+ }
+ }
+ if (i+1 >= objc) {
+ Tcl_SetObjResult(interp,
+ Tcl_NewWideIntObj(dataPtr->defFlags & CLF_VALIDATE ? 1 : 0));
+ }
+ break;
+ case CLOCK_CLEAR_CACHE:
+ ClockConfigureClear(dataPtr);
+ break;
+ case CLOCK_INIT_COMPLETE:
+ {
+ /*
+ * Init completed.
+ * Compile clock ensemble (performance purposes).
+ */
+ Tcl_Command token = Tcl_FindCommand(interp, "::clock",
+ NULL, TCL_GLOBAL_ONLY);
+ if (!token) {
+ return TCL_ERROR;
+ }
+ int ensFlags = 0;
+ if (Tcl_GetEnsembleFlags(interp, token, &ensFlags) != TCL_OK) {
+ return TCL_ERROR;
+ }
+ ensFlags |= ENSEMBLE_COMPILE;
+ if (Tcl_SetEnsembleFlags(interp, token, ensFlags) != TCL_OK) {
+ return TCL_ERROR;
+ }
+ }
+ break;
+ }
+ }
+
+ return TCL_OK;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * ClockGetTZData --
+ *
+ * Retrieves tzdata table for given normalized timezone.
+ *
+ * Results:
+ * Returns a tcl object with tzdata.
+ *
+ * Side effects:
+ * The tzdata can be cached in ClockClientData structure.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static inline Tcl_Obj *
+ClockGetTZData(
+ void *clientData, /* Opaque pointer to literal pool, etc. */
+ Tcl_Interp *interp, /* Tcl interpreter */
+ Tcl_Obj *timezoneObj) /* Name of the timezone */
+{
+ ClockClientData *dataPtr = (ClockClientData *)clientData;
+ Tcl_Obj *ret, **out = NULL;
+
+ /* if cached (if already setup this one) */
+ if ( timezoneObj == dataPtr->lastSetupTimeZone
+ || timezoneObj == dataPtr->lastSetupTimeZoneUnnorm
+ ) {
+ if (dataPtr->lastSetupTZData != NULL) {
+ return dataPtr->lastSetupTZData;
+ }
+ out = &dataPtr->lastSetupTZData;
+ }
+ /* differentiate GMT and system zones, because used often */
+ /* simple caching, because almost used the tz-data of last timezone
+ */
+ if (timezoneObj == dataPtr->systemTimeZone) {
+ if (dataPtr->systemSetupTZData != NULL) {
+ return dataPtr->systemSetupTZData;
+ }
+ out = &dataPtr->systemSetupTZData;
+ }
+ else
+ if ( timezoneObj == dataPtr->literals[LIT_GMT]
+ || timezoneObj == dataPtr->gmtSetupTimeZoneUnnorm
+ ) {
+ if (dataPtr->gmtSetupTZData != NULL) {
+ return dataPtr->gmtSetupTZData;
+ }
+ out = &dataPtr->gmtSetupTZData;
+ }
+ else
+ if ( timezoneObj == dataPtr->prevSetupTimeZone
+ || timezoneObj == dataPtr->prevSetupTimeZoneUnnorm
+ ) {
+ if (dataPtr->prevSetupTZData != NULL) {
+ return dataPtr->prevSetupTZData;
+ }
+ out = &dataPtr->prevSetupTZData;
+ }
+
+ ret = Tcl_ObjGetVar2(interp, dataPtr->literals[LIT_TZDATA],
+ timezoneObj, TCL_LEAVE_ERR_MSG);
+
+ /* cache using corresponding slot and as last used */
+ if (out != NULL) {
+ Tcl_SetObjRef(*out, ret);
+ }
+ else
+ if (dataPtr->lastSetupTimeZone != timezoneObj) {
+ SavePrevTimezoneObj(dataPtr);
+ Tcl_SetObjRef(dataPtr->lastSetupTimeZone, timezoneObj);
+ Tcl_UnsetObjRef(dataPtr->lastSetupTimeZoneUnnorm);
+ Tcl_SetObjRef(dataPtr->lastSetupTZData, ret);
+ }
+ return ret;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * ClockGetSystemTimeZone --
+ *
+ * Returns system (current) timezone.
+ *
+ * If system zone not yet cached, it executes ::tcl::clock::GetSystemTimeZone
+ * in given interpreter and caches its result.
+ *
+ * Results:
+ * Returns normalized timezone object.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static Tcl_Obj *
+ClockGetSystemTimeZone(
+ void *clientData, /* Opaque pointer to literal pool, etc. */
+ Tcl_Interp *interp) /* Tcl interpreter */
+{
+ ClockClientData *dataPtr = (ClockClientData *)clientData;
+
+ /* if known (cached and same epoch) - return now */
+ if (dataPtr->systemTimeZone != NULL
+ && dataPtr->lastTZEpoch == TzsetIfNecessary()) {
+ return dataPtr->systemTimeZone;
+ }
+
+ Tcl_UnsetObjRef(dataPtr->systemTimeZone);
+ Tcl_UnsetObjRef(dataPtr->systemSetupTZData);
+
+ if (Tcl_EvalObjv(interp, 1, &dataPtr->literals[LIT_GETSYSTEMTIMEZONE], 0) != TCL_OK) {
+ return NULL;
+ }
+ if (dataPtr->systemTimeZone == NULL) {
+ Tcl_SetObjRef(dataPtr->systemTimeZone, Tcl_GetObjResult(interp));
+ }
+ Tcl_ResetResult(interp);
+ return dataPtr->systemTimeZone;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * ClockSetupTimeZone --
+ *
+ * Sets up the timezone. Loads tzdata, etc.
+ *
+ * Results:
+ * Returns normalized timezone object.
+ *
+ *----------------------------------------------------------------------
+ */
+
+Tcl_Obj *
+ClockSetupTimeZone(
+ void *clientData, /* Opaque pointer to literal pool, etc. */
+ Tcl_Interp *interp, /* Tcl interpreter */
+ Tcl_Obj *timezoneObj)
+{
+ ClockClientData *dataPtr = (ClockClientData *)clientData;
+ int loaded;
+ Tcl_Obj *callargs[2];
+
+ /* if cached (if already setup this one) */
+ if ( timezoneObj == dataPtr->literals[LIT_GMT]
+ && dataPtr->gmtSetupTZData != NULL
+ ) {
+ return timezoneObj;
+ }
+ if ( ( timezoneObj == dataPtr->lastSetupTimeZone
+ || timezoneObj == dataPtr->lastSetupTimeZoneUnnorm
+ ) && dataPtr->lastSetupTimeZone != NULL
+ ) {
+ return dataPtr->lastSetupTimeZone;
+ }
+ if ( ( timezoneObj == dataPtr->prevSetupTimeZone
+ || timezoneObj == dataPtr->prevSetupTimeZoneUnnorm
+ ) && dataPtr->prevSetupTimeZone != NULL
+ ) {
+ return dataPtr->prevSetupTimeZone;
+ }
+
+ /* differentiate normalized (last, GMT and system) zones, because used often and already set */
+ callargs[1] = NormTimezoneObj(dataPtr, timezoneObj, &loaded);
+ /* if loaded (setup already called for this TZ) */
+ if (loaded) {
+ return callargs[1];
+ }
+
+ /* before setup just take a look in TZData variable */
+ if (Tcl_ObjGetVar2(interp, dataPtr->literals[LIT_TZDATA], timezoneObj, 0)) {
+ /* put it to last slot and return normalized */
+ TimezoneLoaded(dataPtr, callargs[1], timezoneObj);
+ return callargs[1];
+ }
+ /* setup now */
+ callargs[0] = dataPtr->literals[LIT_SETUPTIMEZONE];
+ if (Tcl_EvalObjv(interp, 2, callargs, 0) == TCL_OK) {
+ /* save unnormalized last used */
+ Tcl_SetObjRef(dataPtr->lastSetupTimeZoneUnnorm, timezoneObj);
+ return callargs[1];
+ }
+ return NULL;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * ClockFormatNumericTimeZone --
+ *
+ * Formats a time zone as +hhmmss
+ *
+ * Parameters:
+ * z - Time zone in seconds east of Greenwich
+ *
+ * Results:
+ * Returns the time zone object (formatted in a numeric form)
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+Tcl_Obj *
+ClockFormatNumericTimeZone(int z) {
+ char buf[12+1], *p;
+
+ if ( z < 0 ) {
+ z = -z;
+ *buf = '-';
+ } else {
+ *buf = '+';
+ }
+ TclItoAw(buf+1, z / 3600, '0', 2); z %= 3600;
+ p = TclItoAw(buf+3, z / 60, '0', 2); z %= 60;
+ if (z != 0) {
+ p = TclItoAw(buf+5, z, '0', 2);
+ }
+ return Tcl_NewStringObj(buf, p - buf);
}
/*
@@ -288,11 +1405,11 @@ TclClockInit(
* is available.
*
* Usage:
- * ::tcl::clock::ConvertUTCToLocal dictionary tzdata changeover
+ * ::tcl::clock::ConvertUTCToLocal dictionary timezone changeover
*
* Parameters:
* dict - Dictionary containing a 'localSeconds' entry.
- * tzdata - Time zone data
+ * timezone - Time zone
* changeover - Julian Day of the adoption of the Gregorian calendar.
*
* Results:
@@ -321,12 +1438,13 @@ ClockConvertlocaltoutcObjCmd(
int created = 0;
int status;
+ fields.tzName = NULL;
/*
* Check params and convert time.
*/
if (objc != 4) {
- Tcl_WrongNumArgs(interp, 1, objv, "dict tzdata changeover");
+ Tcl_WrongNumArgs(interp, 1, objv, "dict timezone changeover");
return TCL_ERROR;
}
dict = objv[1];
@@ -342,7 +1460,7 @@ ClockConvertlocaltoutcObjCmd(
if ((TclGetWideIntFromObj(interp, secondsObj,
&fields.localSeconds) != TCL_OK)
|| (TclGetIntFromObj(interp, objv[3], &changeover) != TCL_OK)
- || ConvertLocalToUTC(interp, &fields, objv[2], changeover)) {
+ || ConvertLocalToUTC(clientData, interp, &fields, objv[2], changeover)) {
return TCL_ERROR;
}
@@ -376,12 +1494,11 @@ ClockConvertlocaltoutcObjCmd(
* formatting a date, and populates a dictionary with them.
*
* Usage:
- * ::tcl::clock::GetDateFields seconds tzdata changeover
+ * ::tcl::clock::GetDateFields seconds timezone changeover
*
* Parameters:
* seconds - Time expressed in seconds from the Posix epoch.
- * tzdata - Time zone data of the time zone in which time is to be
- * expressed.
+ * timezone - Time zone in which time is to be expressed.
* changeover - Julian Day Number at which the current locale adopted
* the Gregorian calendar
*
@@ -410,12 +1527,14 @@ ClockGetdatefieldsObjCmd(
Tcl_Obj *const *lit = data->literals;
int changeover;
+ fields.tzName = NULL;
+
/*
* Check params.
*/
if (objc != 4) {
- Tcl_WrongNumArgs(interp, 1, objv, "seconds tzdata changeover");
+ Tcl_WrongNumArgs(interp, 1, objv, "seconds timezone changeover");
return TCL_ERROR;
}
if (TclGetWideIntFromObj(interp, objv[1], &fields.seconds) != TCL_OK
@@ -433,30 +1552,14 @@ ClockGetdatefieldsObjCmd(
return TCL_ERROR;
}
- /*
- * Convert UTC time to local.
- */
+ /* Extract fields */
- if (ConvertUTCToLocal(interp, &fields, objv[2], changeover) != TCL_OK) {
+ if (ClockGetDateFields(clientData, interp, &fields, objv[2],
+ changeover) != TCL_OK) {
return TCL_ERROR;
}
- /*
- * Extract Julian day. Always round the quotient down by subtracting 1
- * when the remainder is negative (i.e. if the quotient was rounded up).
- */
-
- fields.julianDay = (int) ((fields.localSeconds / SECONDS_PER_DAY) -
- ((fields.localSeconds % SECONDS_PER_DAY) < 0) +
- JULIAN_DAY_POSIX_EPOCH);
-
- /*
- * Convert to Julian or Gregorian calendar.
- */
-
- GetGregorianEraYearDay(&fields, changeover);
- GetMonthDay(&fields);
- GetYearWeekDay(&fields, changeover);
+ /* Make dict of fields */
dict = Tcl_NewDictObj();
Tcl_DictObjPut(NULL, dict, lit[LIT_LOCALSECONDS],
@@ -495,6 +1598,58 @@ ClockGetdatefieldsObjCmd(
/*
*----------------------------------------------------------------------
*
+ * ClockGetDateFields --
+ *
+ * Converts given UTC time (seconds in a TclDateFields structure)
+ * to local time and determines the values that clock routines will
+ * use in scanning or formatting a date.
+ *
+ * Results:
+ * Date-time values are stored in structure "fields".
+ * Returns a standard Tcl result.
+ *
+ *----------------------------------------------------------------------
+ */
+
+int
+ClockGetDateFields(
+ void *clientData, /* Client data of the interpreter */
+ Tcl_Interp *interp, /* Tcl interpreter */
+ TclDateFields *fields, /* Pointer to result fields, where
+ * fields->seconds contains date to extract */
+ Tcl_Obj *timezoneObj, /* Time zone object or NULL for gmt */
+ int changeover) /* Julian Day Number */
+{
+ /*
+ * Convert UTC time to local.
+ */
+
+ if (ConvertUTCToLocal(clientData, interp, fields, timezoneObj,
+ changeover) != TCL_OK) {
+ return TCL_ERROR;
+ }
+
+ /*
+ * Extract Julian day and seconds of the day.
+ */
+
+ ClockExtractJDAndSODFromSeconds(fields->julianDay, fields->secondOfDay,
+ fields->localSeconds);
+
+ /*
+ * Convert to Julian or Gregorian calendar.
+ */
+
+ GetGregorianEraYearDay(fields, changeover);
+ GetMonthDay(fields);
+ GetYearWeekDay(fields, changeover);
+
+ return TCL_OK;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
* ClockGetjuliandayfromerayearmonthdayObjCmd --
*
* Tcl command that converts a time from era-year-month-day to a Julian
@@ -569,6 +1724,8 @@ ClockGetjuliandayfromerayearmonthdayObjCmd(
int status;
int isBce = 0;
+ fields.tzName = NULL;
+
/*
* Check params.
*/
@@ -653,6 +1810,8 @@ ClockGetjuliandayfromerayearweekdayObjCmd(
int status;
int isBce = 0;
+ fields.tzName = NULL;
+
/*
* Check params.
*/
@@ -720,18 +1879,63 @@ ClockGetjuliandayfromerayearweekdayObjCmd(
static int
ConvertLocalToUTC(
+ void *clientData, /* Client data of the interpreter */
Tcl_Interp *interp, /* Tcl interpreter */
TclDateFields *fields, /* Fields of the time */
- Tcl_Obj *tzdata, /* Time zone data */
+ Tcl_Obj *timezoneObj, /* Time zone */
int changeover) /* Julian Day of the Gregorian transition */
{
+ ClockClientData *dataPtr = (ClockClientData *)clientData;
+ Tcl_Obj *tzdata; /* Time zone data */
Tcl_Size rowc; /* Number of rows in tzdata */
Tcl_Obj **rowv; /* Pointers to the rows */
+ Tcl_WideInt seconds;
+ ClockLastTZOffs * ltzoc = NULL;
+
+ /* fast phase-out for shared GMT-object (don't need to convert UTC 2 UTC) */
+ if (timezoneObj == dataPtr->literals[LIT_GMT]) {
+ fields->seconds = fields->localSeconds;
+ fields->tzOffset = 0;
+ return TCL_OK;
+ }
+
+ /*
+ * Check cacheable conversion could be used
+ * (last-period UTC2Local cache within the same TZ and seconds)
+ */
+ for (rowc = 0; rowc < 2; rowc++) {
+ ltzoc = &dataPtr->lastTZOffsCache[rowc];
+ if (timezoneObj != ltzoc->timezoneObj || changeover != ltzoc->changeover) {
+ ltzoc = NULL;
+ continue;
+ }
+ seconds = fields->localSeconds - ltzoc->tzOffset;
+ if ( seconds >= ltzoc->rangesVal[0]
+ && seconds < ltzoc->rangesVal[1]
+ ) {
+ /* the same time zone and offset (UTC time inside the last minute) */
+ fields->tzOffset = ltzoc->tzOffset;
+ fields->seconds = seconds;
+ return TCL_OK;
+ }
+ /* in the DST-hole (because of the check above) - correct localSeconds */
+ if (fields->localSeconds == ltzoc->localSeconds) {
+ /* the same time zone and offset (but we'll shift local-time) */
+ fields->tzOffset = ltzoc->tzOffset;
+ fields->seconds = seconds;
+ goto dstHole;
+ }
+ }
/*
* Unpack the tz data.
*/
+ tzdata = ClockGetTZData(clientData, interp, timezoneObj);
+ if (tzdata == NULL) {
+ return TCL_ERROR;
+ }
+
if (TclListObjGetElements(interp, tzdata, &rowc, &rowv) != TCL_OK) {
return TCL_ERROR;
}
@@ -742,10 +1946,59 @@ ConvertLocalToUTC(
*/
if (rowc == 0) {
- return ConvertLocalToUTCUsingC(interp, fields, changeover);
+
+ if (ConvertLocalToUTCUsingC(interp, fields, changeover) != TCL_OK) {
+ return TCL_ERROR;
+ };
+
+ /* we cannot cache (ranges unknown yet) - todo: check later the DST-hole here */
+ return TCL_OK;
+
} else {
- return ConvertLocalToUTCUsingTable(interp, fields, rowc, rowv);
+ Tcl_WideInt rangesVal[2];
+
+ if (ConvertLocalToUTCUsingTable(interp, fields, rowc, rowv,
+ rangesVal) != TCL_OK) {
+ return TCL_ERROR;
+ };
+
+ seconds = fields->seconds;
+
+ /* Cache the last conversion */
+ if (ltzoc != NULL) { /* slot was found above */
+ /* timezoneObj and changeover are the same */
+ Tcl_SetObjRef(ltzoc->tzName, fields->tzName); /* may be NULL */
+ } else {
+ /* no TZ in cache - just move second slot down and use the first one */
+ ltzoc = &dataPtr->lastTZOffsCache[0];
+ Tcl_UnsetObjRef(dataPtr->lastTZOffsCache[1].timezoneObj);
+ Tcl_UnsetObjRef(dataPtr->lastTZOffsCache[1].tzName);
+ memcpy(&dataPtr->lastTZOffsCache[1], ltzoc, sizeof(*ltzoc));
+ Tcl_InitObjRef(ltzoc->timezoneObj, timezoneObj);
+ ltzoc->changeover = changeover;
+ Tcl_InitObjRef(ltzoc->tzName, fields->tzName); /* may be NULL */
+ }
+ ltzoc->localSeconds = fields->localSeconds;
+ ltzoc->rangesVal[0] = rangesVal[0];
+ ltzoc->rangesVal[1] = rangesVal[1];
+ ltzoc->tzOffset = fields->tzOffset;
}
+
+
+ /* check DST-hole: if retrieved seconds is out of range */
+ if ( ltzoc->rangesVal[0] > seconds || seconds >= ltzoc->rangesVal[1] ) {
+ dstHole:
+ #if 0
+ printf("given local-time is outside the time-zone (in DST-hole): "
+ "%d - offs %d => %d <= %d < %d\n",
+ (int)fields->localSeconds, fields->tzOffset,
+ (int)ltzoc->rangesVal[0], (int)seconds, (int)ltzoc->rangesVal[1]);
+ #endif
+ /* because we don't know real TZ (we're outsize), just invalidate local
+ * time (which could be verified in ClockValidDate later) */
+ fields->localSeconds = TCL_INV_SECONDS; /* not valid seconds */
+ }
+ return TCL_OK;
}
/*
@@ -770,16 +2023,19 @@ static int
ConvertLocalToUTCUsingTable(
Tcl_Interp *interp, /* Tcl interpreter */
TclDateFields *fields, /* Time to convert, with 'seconds' filled in */
- Tcl_Size rowc, /* Number of points at which time changes */
- Tcl_Obj *const rowv[]) /* Points at which time changes */
+ int rowc, /* Number of points at which time changes */
+ Tcl_Obj *const rowv[], /* Points at which time changes */
+ Tcl_WideInt *rangesVal) /* Return bounds for time period */
{
Tcl_Obj *row;
Tcl_Size cellc;
Tcl_Obj **cellv;
- int have[8];
+ struct {
+ Tcl_Obj *tzName;
+ int tzOffset;
+ } have[8];
int nHave = 0;
- int i;
- int found;
+ Tcl_Size i;
/*
* Perform an initial lookup assuming that local == UTC, and locate the
@@ -791,11 +2047,11 @@ ConvertLocalToUTCUsingTable(
* Saving Time transition.
*/
- found = 0;
fields->tzOffset = 0;
fields->seconds = fields->localSeconds;
- while (!found) {
- row = LookupLastTransition(interp, fields->seconds, rowc, rowv);
+ while (1) {
+ row = LookupLastTransition(interp, fields->seconds, rowc, rowv,
+ rangesVal);
if ((row == NULL)
|| TclListObjGetElements(interp, row, &cellc,
&cellv) != TCL_OK
@@ -803,23 +2059,24 @@ ConvertLocalToUTCUsingTable(
&fields->tzOffset) != TCL_OK) {
return TCL_ERROR;
}
- found = 0;
- for (i = 0; !found && i < nHave; ++i) {
- if (have[i] == fields->tzOffset) {
- found = 1;
- break;
+ for (i = 0; i < nHave; ++i) {
+ if (have[i].tzOffset == fields->tzOffset) {
+ goto found;
}
}
- if (!found) {
- if (nHave == 8) {
- Tcl_Panic("loop in ConvertLocalToUTCUsingTable");
- }
- have[nHave++] = fields->tzOffset;
+ if (nHave == 8) {
+ Tcl_Panic("loop in ConvertLocalToUTCUsingTable");
}
+ have[nHave].tzName = cellv[3];
+ have[nHave++].tzOffset = fields->tzOffset;
fields->seconds = fields->localSeconds - fields->tzOffset;
}
- fields->tzOffset = have[i];
+
+ found:
+ fields->tzOffset = have[i].tzOffset;
fields->seconds = fields->localSeconds - fields->tzOffset;
+ Tcl_SetObjRef(fields->tzName, have[i].tzName);
+
return TCL_OK;
}
@@ -850,19 +2107,14 @@ ConvertLocalToUTCUsingC(
struct tm timeVal;
int localErrno;
int secondOfDay;
- Tcl_WideInt jsec;
/*
* Convert the given time to a date.
*/
- jsec = fields->localSeconds + JULIAN_SEC_POSIX_EPOCH;
- fields->julianDay = (int) (jsec / SECONDS_PER_DAY);
- secondOfDay = (int)(jsec % SECONDS_PER_DAY);
- if (secondOfDay < 0) {
- secondOfDay += SECONDS_PER_DAY;
- fields->julianDay--;
- }
+ ClockExtractJDAndSODFromSeconds(fields->julianDay, secondOfDay,
+ fields->localSeconds);
+
GetGregorianEraYearDay(fields, changeover);
GetMonthDay(fields);
@@ -921,20 +2173,67 @@ ConvertLocalToUTCUsingC(
*----------------------------------------------------------------------
*/
-static int
+int
ConvertUTCToLocal(
+ void *clientData, /* Client data of the interpreter */
Tcl_Interp *interp, /* Tcl interpreter */
TclDateFields *fields, /* Fields of the time */
- Tcl_Obj *tzdata, /* Time zone data */
+ Tcl_Obj *timezoneObj, /* Time zone */
int changeover) /* Julian Day of the Gregorian transition */
{
+ ClockClientData *dataPtr = (ClockClientData *)clientData;
+ Tcl_Obj *tzdata; /* Time zone data */
Tcl_Size rowc; /* Number of rows in tzdata */
Tcl_Obj **rowv; /* Pointers to the rows */
+ ClockLastTZOffs * ltzoc = NULL;
+
+ /* fast phase-out for shared GMT-object (don't need to convert UTC 2 UTC) */
+ if (timezoneObj == dataPtr->literals[LIT_GMT]) {
+ fields->localSeconds = fields->seconds;
+ fields->tzOffset = 0;
+ if (dataPtr->gmtTZName == NULL) {
+ Tcl_Obj *tzName;
+ tzdata = ClockGetTZData(clientData, interp, timezoneObj);
+ if ( TclListObjGetElements(interp, tzdata, &rowc, &rowv) != TCL_OK
+ || Tcl_ListObjIndex(interp, rowv[0], 3, &tzName) != TCL_OK) {
+ return TCL_ERROR;
+ }
+ Tcl_SetObjRef(dataPtr->gmtTZName, tzName);
+ }
+ Tcl_SetObjRef(fields->tzName, dataPtr->gmtTZName);
+ return TCL_OK;
+ }
+
+ /*
+ * Check cacheable conversion could be used
+ * (last-period UTC2Local cache within the same TZ and seconds)
+ */
+ for (rowc = 0; rowc < 2; rowc++) {
+ ltzoc = &dataPtr->lastTZOffsCache[rowc];
+ if (timezoneObj != ltzoc->timezoneObj || changeover != ltzoc->changeover) {
+ ltzoc = NULL;
+ continue;
+ }
+ if ( fields->seconds >= ltzoc->rangesVal[0]
+ && fields->seconds < ltzoc->rangesVal[1]
+ ) {
+ /* the same time zone and offset (UTC time inside the last minute) */
+ fields->tzOffset = ltzoc->tzOffset;
+ fields->localSeconds = fields->seconds + fields->tzOffset;
+ Tcl_SetObjRef(fields->tzName, ltzoc->tzName);
+ return TCL_OK;
+ }
+ }
/*
* Unpack the tz data.
*/
+ tzdata = ClockGetTZData(clientData, interp, timezoneObj);
+ if (tzdata == NULL) {
+ return TCL_ERROR;
+ }
+
if (TclListObjGetElements(interp, tzdata, &rowc, &rowv) != TCL_OK) {
return TCL_ERROR;
}
@@ -945,10 +2244,47 @@ ConvertUTCToLocal(
*/
if (rowc == 0) {
- return ConvertUTCToLocalUsingC(interp, fields, changeover);
+
+ if (ConvertUTCToLocalUsingC(interp, fields, changeover) != TCL_OK) {
+ return TCL_ERROR;
+ }
+
+ /* signal we need to revalidate TZ epoch next time fields gets used. */
+ fields->flags |= CLF_CTZ;
+
+ /* we cannot cache (ranges unknown yet) */
} else {
- return ConvertUTCToLocalUsingTable(interp, fields, rowc, rowv);
+ Tcl_WideInt rangesVal[2];
+
+ if (ConvertUTCToLocalUsingTable(interp, fields, rowc, rowv,
+ rangesVal) != TCL_OK) {
+ return TCL_ERROR;
+ }
+
+ /* converted using table (TZ isn't :localtime) */
+ fields->flags &= ~CLF_CTZ;
+
+ /* Cache the last conversion */
+ if (ltzoc != NULL) { /* slot was found above */
+ /* timezoneObj and changeover are the same */
+ Tcl_SetObjRef(ltzoc->tzName, fields->tzName);
+ } else {
+ /* no TZ in cache - just move second slot down and use the first one */
+ ltzoc = &dataPtr->lastTZOffsCache[0];
+ Tcl_UnsetObjRef(dataPtr->lastTZOffsCache[1].timezoneObj);
+ Tcl_UnsetObjRef(dataPtr->lastTZOffsCache[1].tzName);
+ memcpy(&dataPtr->lastTZOffsCache[1], ltzoc, sizeof(*ltzoc));
+ Tcl_InitObjRef(ltzoc->timezoneObj, timezoneObj);
+ ltzoc->changeover = changeover;
+ Tcl_InitObjRef(ltzoc->tzName, fields->tzName);
+ }
+ ltzoc->localSeconds = fields->localSeconds;
+ ltzoc->rangesVal[0] = rangesVal[0];
+ ltzoc->rangesVal[1] = rangesVal[1];
+ ltzoc->tzOffset = fields->tzOffset;
}
+
+ return TCL_OK;
}
/*
@@ -975,7 +2311,8 @@ ConvertUTCToLocalUsingTable(
TclDateFields *fields, /* Fields of the date */
Tcl_Size rowc, /* Number of rows in the conversion table
* (>= 1) */
- Tcl_Obj *const rowv[]) /* Rows of the conversion table */
+ Tcl_Obj *const rowv[], /* Rows of the conversion table */
+ Tcl_WideInt *rangesVal) /* Return bounds for time period */
{
Tcl_Obj *row; /* Row containing the current information */
Tcl_Size cellc; /* Count of cells in the row (must be 4) */
@@ -985,7 +2322,7 @@ ConvertUTCToLocalUsingTable(
* Look up the nearest transition time.
*/
- row = LookupLastTransition(interp, fields->seconds, rowc, rowv);
+ row = LookupLastTransition(interp, fields->seconds, rowc, rowv, rangesVal);
if (row == NULL ||
TclListObjGetElements(interp, row, &cellc, &cellv) != TCL_OK ||
TclGetIntFromObj(interp, cellv[1], &fields->tzOffset) != TCL_OK) {
@@ -996,8 +2333,7 @@ ConvertUTCToLocalUsingTable(
* Convert the time.
*/
- fields->tzName = cellv[3];
- Tcl_IncrRefCount(fields->tzName);
+ Tcl_SetObjRef(fields->tzName, cellv[3]);
fields->localSeconds = fields->seconds + fields->tzOffset;
return TCL_OK;
}
@@ -1030,7 +2366,7 @@ ConvertUTCToLocalUsingC(
time_t tock;
struct tm *timeVal; /* Time after conversion */
int diff; /* Time zone diff local-Greenwich */
- char buffer[16]; /* Buffer for time zone name */
+ char buffer[16], *p; /* Buffer for time zone name */
/*
* Use 'localtime' to determine local year, month, day, time of day.
@@ -1067,7 +2403,7 @@ ConvertUTCToLocalUsingC(
* Convert that value to seconds.
*/
- fields->localSeconds = (((fields->julianDay * (Tcl_WideInt) 24
+ fields->localSeconds = (((fields->julianDay * 24LL
+ timeVal->tm_hour) * 60 + timeVal->tm_min) * 60
+ timeVal->tm_sec) - JULIAN_SEC_POSIX_EPOCH;
@@ -1083,15 +2419,12 @@ ConvertUTCToLocalUsingC(
} else {
*buffer = '+';
}
- snprintf(buffer+1, sizeof(buffer) - 1, "%02d", diff / 3600);
- diff %= 3600;
- snprintf(buffer+3, sizeof(buffer) - 3, "%02d", diff / 60);
- diff %= 60;
- if (diff > 0) {
- snprintf(buffer+5, sizeof(buffer) - 5, "%02d", diff);
+ TclItoAw(buffer+1, diff / 3600, '0', 2); diff %= 3600;
+ p = TclItoAw(buffer+3, diff / 60, '0', 2); diff %= 60;
+ if (diff != 0) {
+ p = TclItoAw(buffer+5, diff, '0', 2);
}
- fields->tzName = Tcl_NewStringObj(buffer, -1);
- Tcl_IncrRefCount(fields->tzName);
+ Tcl_SetObjRef(fields->tzName, Tcl_NewStringObj(buffer, p - buffer));
return TCL_OK;
}
@@ -1109,16 +2442,17 @@ ConvertUTCToLocalUsingC(
*----------------------------------------------------------------------
*/
-static Tcl_Obj *
+Tcl_Obj *
LookupLastTransition(
Tcl_Interp *interp, /* Interpreter for error messages */
Tcl_WideInt tick, /* Time from the epoch */
Tcl_Size rowc, /* Number of rows of tzdata */
- Tcl_Obj *const *rowv) /* Rows in tzdata */
+ Tcl_Obj *const *rowv, /* Rows in tzdata */
+ Tcl_WideInt *rangesVal) /* Return bounds for time period */
{
- Tcl_Size l, u;
+ Tcl_Size l, u;
Tcl_Obj *compObj;
- Tcl_WideInt compVal;
+ Tcl_WideInt compVal, fromVal = LLONG_MIN, toVal = LLONG_MAX;
/*
* Examine the first row to make sure we're in bounds.
@@ -1134,7 +2468,11 @@ LookupLastTransition(
* anyway.
*/
- if (tick < compVal) {
+ if (tick < (fromVal = compVal)) {
+ if (rangesVal) {
+ rangesVal[0] = fromVal;
+ rangesVal[1] = toVal;
+ }
return rowv[0];
}
@@ -1153,10 +2491,17 @@ LookupLastTransition(
}
if (tick >= compVal) {
l = m;
+ fromVal = compVal;
} else {
u = m-1;
+ toVal = compVal;
}
}
+
+ if (rangesVal) {
+ rangesVal[0] = fromVal;
+ rangesVal[1] = toVal;
+ }
return rowv[l];
}
@@ -1187,6 +2532,8 @@ GetYearWeekDay(
TclDateFields temp;
int dayOfFiscalYear;
+ temp.tzName = NULL;
+
/*
* 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.
@@ -1409,7 +2756,7 @@ GetMonthDay(
*----------------------------------------------------------------------
*/
-static void
+void
GetJulianDayFromEraYearWeekDay(
TclDateFields *fields, /* Date to convert */
int changeover) /* Julian Day Number of the Gregorian
@@ -1419,6 +2766,8 @@ GetJulianDayFromEraYearWeekDay(
* given year */
TclDateFields firstWeek;
+ firstWeek.tzName = NULL;
+
/*
* Find January 4 in the ISO8601 year, which will always be in week 1.
*/
@@ -1460,7 +2809,7 @@ GetJulianDayFromEraYearWeekDay(
*----------------------------------------------------------------------
*/
-static void
+void
GetJulianDayFromEraYearMonthDay(
TclDateFields *fields, /* Date to convert */
int changeover) /* Gregorian transition date as a Julian Day */
@@ -1557,6 +2906,61 @@ GetJulianDayFromEraYearMonthDay(
/*
*----------------------------------------------------------------------
*
+ * GetJulianDayFromEraYearDay --
+ *
+ * Given era, year, and dayOfYear (in TclDateFields), and the
+ * Gregorian transition date, computes the Julian Day Number.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * Stores day number in 'julianDay'
+ *
+ *----------------------------------------------------------------------
+ */
+
+
+void
+GetJulianDayFromEraYearDay(
+ TclDateFields *fields, /* Date to convert */
+ int changeover) /* Gregorian transition date as a Julian Day */
+{
+ Tcl_WideInt year, ym1;
+
+ /* Get absolute year number from the civil year */
+ if (fields->isBce) {
+ year = 1 - fields->year;
+ } else {
+ year = fields->year;
+ }
+
+ ym1 = year - 1;
+
+ /* Try the Gregorian calendar first. */
+ fields->gregorian = 1;
+ fields->julianDay =
+ 1721425
+ + fields->dayOfYear
+ + ( 365 * ym1 )
+ + ( ym1 / 4 )
+ - ( ym1 / 100 )
+ + ( ym1 / 400 );
+
+ /* If the date is before the Gregorian change, use the Julian calendar. */
+
+ if ( fields->julianDay < changeover ) {
+ fields->gregorian = 0;
+ fields->julianDay =
+ 1721423
+ + fields->dayOfYear
+ + ( 365 * ym1 )
+ + ( ym1 / 4 );
+ }
+}
+/*
+ *----------------------------------------------------------------------
+ *
* IsGregorianLeapYear --
*
* Tests whether a given year is a leap year, in either Julian or
@@ -1568,7 +2972,7 @@ GetJulianDayFromEraYearMonthDay(
*----------------------------------------------------------------------
*/
-static int
+int
IsGregorianLeapYear(
TclDateFields *fields) /* Date to test */
{
@@ -1767,14 +3171,14 @@ ClockClicksObjCmd(
}
break;
default:
- Tcl_WrongNumArgs(interp, 1, objv, "?-switch?");
+ Tcl_WrongNumArgs(interp, 0, objv, "clock clicks ?-switch?");
return TCL_ERROR;
}
switch (index) {
case CLICKS_MILLIS:
Tcl_GetTime(&now);
- clicks = (Tcl_WideInt)now.sec * 1000 + now.usec / 1000;
+ clicks = now.sec * 1000LL + now.usec / 1000;
break;
case CLICKS_NATIVE:
#ifdef TCL_WIDE_CLICKS
@@ -1821,7 +3225,7 @@ ClockMillisecondsObjCmd(
Tcl_Obj *timeObj;
if (objc != 1) {
- Tcl_WrongNumArgs(interp, 1, objv, NULL);
+ Tcl_WrongNumArgs(interp, 0, objv, "clock milliseconds");
return TCL_ERROR;
}
Tcl_GetTime(&now);
@@ -1857,129 +3261,1295 @@ ClockMicrosecondsObjCmd(
Tcl_Obj *const *objv) /* Parameter values */
{
if (objc != 1) {
- Tcl_WrongNumArgs(interp, 1, objv, NULL);
+ Tcl_WrongNumArgs(interp, 0, objv, "clock microseconds");
return TCL_ERROR;
}
Tcl_SetObjResult(interp, Tcl_NewWideIntObj(TclpGetMicroseconds()));
return TCL_OK;
}
+static inline void
+ClockInitFmtScnArgs(
+ void *clientData,
+ Tcl_Interp *interp,
+ ClockFmtScnCmdArgs *opts)
+{
+ memset(opts, 0, sizeof(*opts));
+ opts->clientData = clientData;
+ opts->interp = interp;
+}
+
/*
*-----------------------------------------------------------------------------
*
- * ClockParseformatargsObjCmd --
+ * ClockParseFmtScnArgs --
*
- * Parses the arguments for [clock format].
+ * Parses the arguments for sub-commands "scan", "format" and "add".
*
- * Results:
- * Returns a standard Tcl result, whose value is a four-element list
- * comprising the time format, the locale, and the timezone.
+ * Note: common options table used here, because for the options often used
+ * the same literals (objects), so it avoids permanent "recompiling" of
+ * option object representation to indexType with another table.
*
- * This function exists because the loop that parses the [clock format]
- * options is a known performance "hot spot", and is implemented in an effort
- * to speed that particular code up.
+ * Results:
+ * Returns a standard Tcl result, and stores parsed options
+ * (format, the locale, timezone and base) in structure "opts".
*
*-----------------------------------------------------------------------------
*/
+#define CLC_FMT_ARGS (0)
+#define CLC_SCN_ARGS (1 << 0)
+#define CLC_ADD_ARGS (1 << 1)
+
static int
-ClockParseformatargsObjCmd(
- void *clientData, /* Client data containing literal pool */
- Tcl_Interp *interp, /* Tcl interpreter */
+ClockParseFmtScnArgs(
+ ClockFmtScnCmdArgs *opts, /* Result vector: format, locale, timezone... */
+ TclDateFields *date, /* Extracted date-time corresponding base
+ * (by scan or add) resp. clockval (by format) */
int objc, /* Parameter count */
- Tcl_Obj *const objv[]) /* Parameter vector */
-{
- ClockClientData *dataPtr = (ClockClientData *)clientData;
- Tcl_Obj **litPtr = dataPtr->literals;
- Tcl_Obj *results[3]; /* Format, locale and timezone */
-#define formatObj results[0]
-#define localeObj results[1]
-#define timezoneObj results[2]
+ Tcl_Obj *const objv[], /* Parameter vector */
+ int flags, /* Flags, differentiates between format, scan, add */
+ const char *syntax /* Syntax of the current command */
+) {
+ Tcl_Interp *interp = opts->interp;
+ ClockClientData *dataPtr = (ClockClientData *)opts->clientData;
int gmtFlag = 0;
- static const char *const options[] = { /* Command line options expected */
- "-format", "-gmt", "-locale",
- "-timezone", NULL };
+ static const char *const options[] = {
+ "-base", "-format", "-gmt", "-locale", "-timezone", "-validate", NULL
+ };
enum optionInd {
- CLOCK_FORMAT_FORMAT, CLOCK_FORMAT_GMT, CLOCK_FORMAT_LOCALE,
- CLOCK_FORMAT_TIMEZONE
+ CLC_ARGS_BASE, CLC_ARGS_FORMAT, CLC_ARGS_GMT, CLC_ARGS_LOCALE,
+ CLC_ARGS_TIMEZONE, CLC_ARGS_VALIDATE
};
int optionIndex; /* Index of an option. */
int saw = 0; /* Flag == 1 if option was seen already. */
- Tcl_WideInt clockVal; /* Clock value - just used to parse. */
int i;
+ Tcl_WideInt baseVal; /* Base time, expressed in seconds from the Epoch */
- /*
- * Args consist of a time followed by keyword-value pairs.
- */
-
- if (objc < 2 || (objc % 2) != 0) {
- Tcl_WrongNumArgs(interp, 0, objv,
- "clock format clockval ?-format string? "
- "?-gmt boolean? ?-locale LOCALE? ?-timezone ZONE?");
- Tcl_SetErrorCode(interp, "CLOCK", "wrongNumArgs", (char *)NULL);
- return TCL_ERROR;
+ if ( flags & (CLC_SCN_ARGS) ) {
+ /* default flags (from configure) */
+ opts->flags |= dataPtr->defFlags & (CLF_VALIDATE);
+ } else {
+ /* clock value (as current base) */
+ opts->baseObj = objv[1];
+ saw |= (1 << CLC_ARGS_BASE);
}
/*
* Extract values for the keywords.
*/
- formatObj = litPtr[LIT__DEFAULT_FORMAT];
- localeObj = litPtr[LIT_C];
- timezoneObj = litPtr[LIT__NIL];
for (i = 2; i < objc; i+=2) {
- if (Tcl_GetIndexFromObj(interp, objv[i], options, "option", 0,
- &optionIndex) != TCL_OK) {
- Tcl_SetErrorCode(interp, "CLOCK", "badOption",
- TclGetString(objv[i]), (char *)NULL);
- return TCL_ERROR;
+ /* bypass integers (offsets) by "clock add" */
+ if (flags & CLC_ADD_ARGS) {
+ Tcl_WideInt num;
+ if (TclGetWideIntFromObj(NULL, objv[i], &num) == TCL_OK) {
+ continue;
+ }
+ }
+ /* get option */
+ if (Tcl_GetIndexFromObj(interp, objv[i], options,
+ "option", 0, &optionIndex) != TCL_OK) {
+ goto badOptionMsg;
+ }
+ /* if already specified */
+ if (saw & (1 << optionIndex)) {
+ if ( !(flags & CLC_SCN_ARGS)
+ && optionIndex == CLC_ARGS_BASE) {
+ goto badOptionMsg;
+ }
+ Tcl_SetObjResult(interp, Tcl_ObjPrintf(
+ "bad option \"%s\": doubly present",
+ TclGetString(objv[i]))
+ );
+ goto badOption;
}
switch (optionIndex) {
- case CLOCK_FORMAT_FORMAT:
- formatObj = objv[i+1];
+ case CLC_ARGS_FORMAT:
+ if (flags & CLC_ADD_ARGS) {
+ goto badOptionMsg;
+ }
+ opts->formatObj = objv[i+1];
break;
- case CLOCK_FORMAT_GMT:
+ case CLC_ARGS_GMT:
if (Tcl_GetBooleanFromObj(interp, objv[i+1], &gmtFlag) != TCL_OK){
return TCL_ERROR;
}
break;
- case CLOCK_FORMAT_LOCALE:
- localeObj = objv[i+1];
+ case CLC_ARGS_LOCALE:
+ opts->localeObj = objv[i+1];
+ break;
+ case CLC_ARGS_TIMEZONE:
+ opts->timezoneObj = objv[i+1];
+ break;
+ case CLC_ARGS_BASE:
+ opts->baseObj = objv[i+1];
break;
- case CLOCK_FORMAT_TIMEZONE:
- timezoneObj = objv[i+1];
+ case CLC_ARGS_VALIDATE:
+ if ( !(flags & CLC_SCN_ARGS) ) {
+ goto badOptionMsg;
+ } else {
+ int val;
+ if (Tcl_GetBooleanFromObj(interp, objv[i+1], &val) != TCL_OK) {
+ return TCL_ERROR;
+ }
+ if (val) {
+ opts->flags |= CLF_VALIDATE;
+ } else {
+ opts->flags &= ~CLF_VALIDATE;
+ }
+ }
break;
}
- saw |= 1 << optionIndex;
+ saw |= (1 << optionIndex);
}
/*
* Check options.
*/
- if (TclGetWideIntFromObj(interp, objv[1], &clockVal) != TCL_OK) {
- return TCL_ERROR;
- }
- if ((saw & (1 << CLOCK_FORMAT_GMT))
- && (saw & (1 << CLOCK_FORMAT_TIMEZONE))) {
- Tcl_SetObjResult(interp, litPtr[LIT_CANNOT_USE_GMT_AND_TIMEZONE]);
+ if ((saw & (1 << CLC_ARGS_GMT))
+ && (saw & (1 << CLC_ARGS_TIMEZONE))) {
+ Tcl_SetObjResult(interp, Tcl_NewStringObj("cannot use -gmt and -timezone in same call", -1));
Tcl_SetErrorCode(interp, "CLOCK", "gmtWithTimezone", (char *)NULL);
return TCL_ERROR;
}
if (gmtFlag) {
- timezoneObj = litPtr[LIT_GMT];
+ opts->timezoneObj = dataPtr->literals[LIT_GMT];
+ }
+ else
+ /* If time zone not specified use system time zone */
+ if ( opts->timezoneObj == NULL
+ || TclGetString(opts->timezoneObj) == NULL
+ || opts->timezoneObj->length == 0
+ ) {
+ opts->timezoneObj = ClockGetSystemTimeZone(opts->clientData, interp);
+ if (opts->timezoneObj == NULL) {
+ return TCL_ERROR;
+ }
+ }
+
+ /* Setup timezone (normalize object if needed and load TZ on demand) */
+
+ opts->timezoneObj = ClockSetupTimeZone(opts->clientData, interp, opts->timezoneObj);
+ if (opts->timezoneObj == NULL) {
+ return TCL_ERROR;
+ }
+
+ /* Base (by scan or add) or clock value (by format) */
+
+ if (opts->baseObj != NULL) {
+ Tcl_Obj *baseObj = opts->baseObj;
+ /* bypass integer recognition if looks like option "-now" */
+ if (
+ (baseObj->length == 4 && baseObj->bytes && *(baseObj->bytes+1) == 'n') ||
+ TclGetWideIntFromObj(NULL, baseObj, &baseVal) != TCL_OK
+ ) {
+
+ /* we accept "-now" as current date-time */
+ static const char *const nowOpts[] = {
+ "-now", NULL
+ };
+ int idx;
+ if (Tcl_GetIndexFromObj(NULL, baseObj, nowOpts, "seconds or -now",
+ TCL_EXACT, &idx) == TCL_OK
+ ) {
+ goto baseNow;
+ }
+
+ Tcl_SetObjResult(interp, Tcl_ObjPrintf(
+ "expected integer but got \"%s\"",
+ Tcl_GetString(baseObj)));
+ Tcl_SetErrorCode(interp, "TCL", "VALUE", "INTEGER", (char *)NULL);
+ i = 1;
+ goto badOption;
+ }
+ /*
+ * Seconds could be an unsigned number that overflowed. Make sure
+ * that it isn't. Additionally it may be too complex to calculate
+ * julianday etc (forwards/backwards) by too large/small values, thus
+ * just let accept a bit shorter values to avoid overflow.
+ * Note the year is currently an integer, thus avoid to overflow it also.
+ */
+
+ if ( baseObj->typePtr == &tclBignumType
+ || baseVal < TCL_MIN_SECONDS || baseVal > TCL_MAX_SECONDS
+ ) {
+ Tcl_SetObjResult(interp, dataPtr->literals[LIT_INTEGER_VALUE_TOO_LARGE]);
+ return TCL_ERROR;
+ }
+
+ } else {
+
+baseNow:
+ {
+ Tcl_Time now;
+ Tcl_GetTime(&now);
+ baseVal = (Tcl_WideInt) now.sec;
+ }
+ }
+
+ /*
+ * Extract year, month and day from the base time for the parser to use as
+ * defaults
+ */
+
+ /* check base fields already cached (by TZ, last-second cache) */
+ if ( dataPtr->lastBase.timezoneObj == opts->timezoneObj
+ && dataPtr->lastBase.date.seconds == baseVal
+ && (!(dataPtr->lastBase.date.flags & CLF_CTZ)
+ || dataPtr->lastTZEpoch == TzsetIfNecessary())
+ ) {
+ memcpy(date, &dataPtr->lastBase.date, ClockCacheableDateFieldsSize);
+ } else {
+ /* extact fields from base */
+ date->seconds = baseVal;
+ if (ClockGetDateFields(opts->clientData, interp, date, opts->timezoneObj,
+ GREGORIAN_CHANGE_DATE) != TCL_OK) { /* TODO - GREGORIAN_CHANGE_DATE should be locale-dependent */
+ return TCL_ERROR;
+ }
+ /* cache last base */
+ memcpy(&dataPtr->lastBase.date, date, ClockCacheableDateFieldsSize);
+ Tcl_SetObjRef(dataPtr->lastBase.timezoneObj, opts->timezoneObj);
+ }
+
+ return TCL_OK;
+
+badOptionMsg:
+
+ Tcl_SetObjResult(interp, Tcl_ObjPrintf(
+ "bad option \"%s\": must be %s",
+ TclGetString(objv[i]), syntax)
+ );
+
+badOption:
+
+ Tcl_SetErrorCode(interp, "CLOCK", "badOption",
+ (i < objc) ? Tcl_GetString(objv[i]) : (char *)NULL, (char *)NULL);
+
+ return TCL_ERROR;
+}
+
+/*----------------------------------------------------------------------
+ *
+ * ClockFormatObjCmd -- , clock format --
+ *
+ * This function is invoked to process the Tcl "clock format" command.
+ *
+ * Formats a count of seconds since the Posix Epoch as a time of day.
+ *
+ * The 'clock format' command formats times of day for output. Refer
+ * to the user documentation to see what it does.
+ *
+ * Results:
+ * Returns a standard Tcl result.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+int
+ClockFormatObjCmd(
+ void *clientData, /* Client data containing literal pool */
+ Tcl_Interp *interp, /* Tcl interpreter */
+ int objc, /* Parameter count */
+ Tcl_Obj *const objv[]) /* Parameter values */
+{
+ ClockClientData *dataPtr = (ClockClientData *)clientData;
+
+ static const char *syntax = "clock format clockval|-now "
+ "?-format string? "
+ "?-gmt boolean? "
+ "?-locale LOCALE? ?-timezone ZONE?";
+ int ret;
+ ClockFmtScnCmdArgs opts; /* Format, locale, timezone and base */
+ DateFormat dateFmt; /* Common structure used for formatting */
+
+ /* even number of arguments */
+ if ((objc & 1) == 1) {
+ Tcl_WrongNumArgs(interp, 0, objv, syntax);
+ Tcl_SetErrorCode(interp, "CLOCK", "wrongNumArgs", (char *)NULL);
+ return TCL_ERROR;
+ }
+
+ memset(&dateFmt, 0, sizeof(dateFmt));
+
+ /*
+ * Extract values for the keywords.
+ */
+
+ ClockInitFmtScnArgs(clientData, interp, &opts);
+ ret = ClockParseFmtScnArgs(&opts, &dateFmt.date, objc, objv,
+ CLC_FMT_ARGS, "-format, -gmt, -locale, or -timezone");
+ if (ret != TCL_OK) {
+ goto done;
+ }
+
+ /* Default format */
+ if (opts.formatObj == NULL) {
+ opts.formatObj = dataPtr->literals[LIT__DEFAULT_FORMAT];
+ }
+
+ /* Use compiled version of Format - */
+
+ ret = ClockFormat(&dateFmt, &opts);
+
+done:
+
+ Tcl_UnsetObjRef(dateFmt.date.tzName);
+
+ if (ret != TCL_OK) {
+ return ret;
+ }
+
+ return TCL_OK;
+}
+
+/*----------------------------------------------------------------------
+ *
+ * ClockScanObjCmd -- , clock scan --
+ *
+ * This function is invoked to process the Tcl "clock scan" command.
+ *
+ * Inputs a count of seconds since the Posix Epoch as a time of day.
+ *
+ * The 'clock scan' command scans times of day on input. Refer to the
+ * user documentation to see what it does.
+ *
+ * Results:
+ * Returns a standard Tcl result.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+int
+ClockScanObjCmd(
+ void *clientData, /* Client data containing literal pool */
+ Tcl_Interp *interp, /* Tcl interpreter */
+ int objc, /* Parameter count */
+ Tcl_Obj *const objv[]) /* Parameter values */
+{
+ static const char *syntax = "clock scan string "
+ "?-base seconds? "
+ "?-format string? "
+ "?-gmt boolean? "
+ "?-locale LOCALE? ?-timezone ZONE? ?-validate boolean?";
+ int ret;
+ ClockFmtScnCmdArgs opts; /* Format, locale, timezone and base */
+ DateInfo yy; /* Common structure used for parsing */
+ DateInfo *info = &yy;
+
+ /* even number of arguments */
+ if ((objc & 1) == 1) {
+ Tcl_WrongNumArgs(interp, 0, objv, syntax);
+ Tcl_SetErrorCode(interp, "CLOCK", "wrongNumArgs", (char *)NULL);
+ return TCL_ERROR;
+ }
+
+ ClockInitDateInfo(&yy);
+
+ /*
+ * Extract values for the keywords.
+ */
+
+ ClockInitFmtScnArgs(clientData, interp, &opts);
+ ret = ClockParseFmtScnArgs(&opts, &yy.date, objc, objv,
+ CLC_SCN_ARGS, "-base, -format, -gmt, -locale, -timezone or -validate");
+ if (ret != TCL_OK) {
+ goto done;
+ }
+
+ /* seconds are in localSeconds (relative base date), so reset time here */
+ yyHour = yyMinutes = yySeconds = yySecondOfDay = 0; yyMeridian = MER24;
+
+ /* If free scan */
+ if (opts.formatObj == NULL) {
+ /* Use compiled version of FreeScan - */
+
+ /* [SB] TODO: Perhaps someday we'll localize the legacy code. Right now, it's not localized. */
+ if (opts.localeObj != NULL) {
+ Tcl_SetObjResult(interp,
+ Tcl_NewStringObj("legacy [clock scan] does not support -locale", -1));
+ Tcl_SetErrorCode(interp, "CLOCK", "flagWithLegacyFormat", (char *)NULL);
+ ret = TCL_ERROR;
+ goto done;
+ }
+ ret = ClockFreeScan(&yy, objv[1], &opts);
+ }
+ else {
+ /* Use compiled version of Scan - */
+
+ ret = ClockScan(&yy, objv[1], &opts);
+ }
+
+ if (ret != TCL_OK) {
+ goto done;
+ }
+
+ /*
+ * If no GMT and not free-scan (where valid stage 1 is done in-between),
+ * validate with stage 1 before local time conversion, otherwise it may
+ * adjust date/time tokens to valid values
+ */
+ if ( (opts.flags & CLF_VALIDATE_S1) &&
+ info->flags & (CLF_ASSEMBLE_SECONDS|CLF_LOCALSEC)
+ ) {
+ ret = ClockValidDate(&yy, &opts, CLF_VALIDATE_S1);
+ if (ret != TCL_OK) {
+ goto done;
+ }
+ }
+
+ /* Convert date info structure into UTC seconds */
+
+ ret = ClockScanCommit(&yy, &opts);
+ if (ret != TCL_OK) {
+ goto done;
+ }
+
+ /* Apply remaining validation rules, if expected */
+ if ( (opts.flags & CLF_VALIDATE) ) {
+ ret = ClockValidDate(&yy, &opts, opts.flags & CLF_VALIDATE);
+ if (ret != TCL_OK) {
+ goto done;
+ }
+ }
+
+done:
+
+ Tcl_UnsetObjRef(yy.date.tzName);
+
+ if (ret != TCL_OK) {
+ return ret;
+ }
+
+ Tcl_SetObjResult(interp, Tcl_NewWideIntObj(yy.date.seconds));
+ return TCL_OK;
+}
+
+/*----------------------------------------------------------------------
+ *
+ * ClockScanCommit --
+ *
+ * Converts date info structure into UTC seconds.
+ *
+ * Results:
+ * Returns a standard Tcl result.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static int
+ClockScanCommit(
+ DateInfo *info, /* Clock scan info structure */
+ ClockFmtScnCmdArgs *opts) /* Format, locale, timezone and base */
+{
+ /* If needed assemble julianDay using year, month, etc. */
+ if (info->flags & CLF_ASSEMBLE_JULIANDAY) {
+ if ((info->flags & CLF_ISO8601WEAK)) {
+ GetJulianDayFromEraYearWeekDay(&yydate, GREGORIAN_CHANGE_DATE);
+ }
+ else
+ if ( !(info->flags & CLF_DAYOFYEAR) /* no day of year */
+ || (info->flags & (CLF_DAYOFMONTH|CLF_MONTH)) /* yymmdd over yyddd */
+ == (CLF_DAYOFMONTH|CLF_MONTH)
+ ) {
+ GetJulianDayFromEraYearMonthDay(&yydate, GREGORIAN_CHANGE_DATE);
+ } else {
+ GetJulianDayFromEraYearDay(&yydate, GREGORIAN_CHANGE_DATE);
+ }
+ info->flags |= CLF_ASSEMBLE_SECONDS;
+ info->flags &= ~CLF_ASSEMBLE_JULIANDAY;
+ }
+
+ /* some overflow checks */
+ if (info->flags & CLF_JULIANDAY) {
+ ClockClientData *dataPtr = (ClockClientData *)opts->clientData;
+ double curJDN = (double)yydate.julianDay
+ + ((double)yySecondOfDay - SECONDS_PER_DAY/2) / SECONDS_PER_DAY;
+ if (curJDN > dataPtr->maxJDN) {
+ Tcl_SetObjResult(opts->interp, Tcl_NewStringObj(
+ "requested date too large to represent", -1));
+ Tcl_SetErrorCode(opts->interp, "CLOCK", "dateTooLarge", (char *)NULL);
+ return TCL_ERROR;
+ }
+ }
+
+ /* Local seconds to UTC (stored in yydate.seconds) */
+
+ if (info->flags & (CLF_ASSEMBLE_SECONDS)) {
+ yydate.localSeconds =
+ -210866803200LL
+ + ( SECONDS_PER_DAY * yydate.julianDay )
+ + ( yySecondOfDay % SECONDS_PER_DAY );
+ }
+
+ if (info->flags & (CLF_ASSEMBLE_SECONDS|CLF_LOCALSEC)) {
+ if (ConvertLocalToUTC(opts->clientData, opts->interp, &yydate,
+ opts->timezoneObj, GREGORIAN_CHANGE_DATE) != TCL_OK) {
+ return TCL_ERROR;
+ }
+ }
+
+ /* Increment UTC seconds with relative time */
+
+ yydate.seconds += yyRelSeconds;
+
+ return TCL_OK;
+}
+
+/*----------------------------------------------------------------------
+ *
+ * ClockValidDate --
+ *
+ * Validate date info structure for wrong data (e. g. out of ranges).
+ *
+ * Results:
+ * Returns a standard Tcl result.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static int
+ClockValidDate(
+ DateInfo *info, /* Clock scan info structure */
+ ClockFmtScnCmdArgs *opts, /* Scan options */
+ int stage) /* Stage to validate (1, 2 or 3 for both) */
+{
+ const char *errMsg = "", *errCode = "";
+ TclDateFields temp;
+ int tempCpyFlg = 0;
+ ClockClientData *dataPtr = (ClockClientData *)opts->clientData;
+
+ #if 0
+ printf("yyMonth %d, yyDay %d, yyDayOfYear %d, yyHour %d, yyMinutes %d, yySeconds %d, "
+ "yySecondOfDay %d, sec %d, daySec %d, tzOffset %d\n",
+ yyMonth, yyDay, yydate.dayOfYear, yyHour, yyMinutes, yySeconds,
+ yySecondOfDay, (int)yydate.localSeconds, (int)(yydate.localSeconds % SECONDS_PER_DAY),
+ yydate.tzOffset);
+ #endif
+
+ if (!(stage & CLF_VALIDATE_S1) || !(opts->flags & CLF_VALIDATE_S1)) {
+ goto stage_2;
+ }
+ opts->flags &= ~CLF_VALIDATE_S1; /* stage 1 is done */
+
+ /* first year (used later in hath / daysInPriorMonths) */
+ if ((info->flags & (CLF_YEAR|CLF_ISO8601YEAR))) {
+ if ((info->flags & CLF_ISO8601YEAR)) {
+ if ( yydate.iso8601Year < dataPtr->validMinYear
+ || yydate.iso8601Year > dataPtr->validMaxYear ) {
+ errMsg = "invalid iso year"; errCode = "iso year"; goto error;
+ }
+ }
+ if (info->flags & CLF_YEAR) {
+ if ( yyYear < dataPtr->validMinYear
+ || yyYear > dataPtr->validMaxYear ) {
+ errMsg = "invalid year"; errCode = "year"; goto error;
+ }
+ } else if ((info->flags & CLF_ISO8601YEAR)) {
+ yyYear = yydate.iso8601Year; /* used to recognize leap */
+ }
+ if ((info->flags & (CLF_ISO8601YEAR|CLF_YEAR))
+ == (CLF_ISO8601YEAR|CLF_YEAR)) {
+ if (yyYear != yydate.iso8601Year) {
+ errMsg = "ambiguous year"; errCode = "year"; goto error;
+ }
+ }
+ }
+ /* and month (used later in hath) */
+ if (info->flags & CLF_MONTH) {
+ if ( yyMonth < 1 || yyMonth > 12 ) {
+ errMsg = "invalid month"; errCode = "month"; goto error;
+ }
+ }
+ /* day of month */
+ if (info->flags & (CLF_DAYOFMONTH|CLF_DAYOFWEEK)) {
+ if ( yyDay < 1 || yyDay > 31 ) {
+ errMsg = "invalid day"; errCode = "day"; goto error;
+ }
+ else
+ if ( (info->flags & CLF_MONTH) ) {
+ const int *h = hath[IsGregorianLeapYear(&yydate)];
+ if ( yyDay > h[yyMonth-1] ) {
+ errMsg = "invalid day"; goto error;
+ }
+ }
+ }
+ if (info->flags & CLF_DAYOFYEAR) {
+ if ( yydate.dayOfYear < 1
+ || yydate.dayOfYear > daysInPriorMonths[IsGregorianLeapYear(&yydate)][12] ) {
+ errMsg = "invalid day of year"; errCode = "day of year"; goto error;
+ }
+ }
+
+ /* mmdd !~ ddd */
+ if ((info->flags & (CLF_DAYOFYEAR|CLF_DAYOFMONTH|CLF_MONTH))
+ == (CLF_DAYOFYEAR|CLF_DAYOFMONTH|CLF_MONTH)) {
+ if (!tempCpyFlg) {
+ memcpy(&temp, &yydate, sizeof(temp));
+ tempCpyFlg = 1;
+ }
+ GetJulianDayFromEraYearDay(&temp, GREGORIAN_CHANGE_DATE);
+ if (temp.julianDay != yydate.julianDay) {
+ errMsg = "ambiguous day"; errCode = "day"; goto error;
+ }
+ }
+
+ if (info->flags & CLF_TIME) {
+ /* hour */
+ if ( yyHour < 0 || yyHour > ((yyMeridian == MER24) ? 23 : 12) ) {
+ errMsg = "invalid time (hour)"; errCode = "hour"; goto error;
+ }
+ /* minutes */
+ if ( yyMinutes < 0 || yyMinutes > 59 ) {
+ errMsg = "invalid time (minutes)"; errCode = "minutes"; goto error;
+ }
+ /* oldscan could return secondOfDay (parsedTime) -1 by invalid time (ex.: 25:00:00) */
+ if ( yySeconds < 0 || yySeconds > 59 || yySecondOfDay <= -1 ) {
+ errMsg = "invalid time"; errCode = "seconds"; goto error;
+ }
+ }
+
+ if (!(stage & CLF_VALIDATE_S2) || !(opts->flags & CLF_VALIDATE_S2)) {
+ return TCL_OK;
+ }
+ opts->flags &= ~CLF_VALIDATE_S2; /* stage 2 is done */
+
+ /*
+ * Further tests expected ready calculated julianDay (inclusive relative),
+ * and time-zone conversion (local to UTC time).
+ */
+ stage_2:
+
+ /* time, regarding the modifications by the time-zone (looks for given time
+ * in between DST-time hole, so does not exist in this time-zone) */
+ if (info->flags & CLF_TIME) {
+ /*
+ * we don't need to do the backwards time-conversion (UTC to local) and
+ * compare results, because the after conversion (local to UTC) we
+ * should have valid localSeconds (was not invalidated to TCL_INV_SECONDS),
+ * so if it was invalidated - invalid time, outside the time-zone (in DST-hole)
+ */
+ if ( yydate.localSeconds == TCL_INV_SECONDS ) {
+ errMsg = "invalid time (does not exist in this time-zone)";
+ errCode = "out-of-time"; goto error;
+ }
+ }
+
+ /* day of week */
+ if (info->flags & CLF_DAYOFWEEK) {
+ if (!tempCpyFlg) {
+ memcpy(&temp, &yydate, sizeof(temp));
+ tempCpyFlg = 1;
+ }
+ GetYearWeekDay(&temp, GREGORIAN_CHANGE_DATE);
+ if (temp.dayOfWeek != yyDayOfWeek) {
+ errMsg = "invalid day of week"; errCode = "day of week"; goto error;
+ }
+ }
+
+ return TCL_OK;
+
+ error:
+ Tcl_SetObjResult(opts->interp,
+ Tcl_ObjPrintf("unable to convert input string: %s", errMsg));
+ Tcl_SetErrorCode(opts->interp, "CLOCK", "invInpStr", errCode, (char *)NULL);
+ return TCL_ERROR;
+}
+
+/*----------------------------------------------------------------------
+ *
+ * ClockFreeScan --
+ *
+ * Used by ClockScanObjCmd for free scanning without format.
+ *
+ * Results:
+ * Returns a standard Tcl result.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+int
+ClockFreeScan(
+ DateInfo *info, /* Date fields used for parsing & converting
+ * simultaneously a yy-parse structure of the
+ * TclClockFreeScan */
+ Tcl_Obj *strObj, /* String containing the time to scan */
+ ClockFmtScnCmdArgs *opts) /* Command options */
+{
+ Tcl_Interp *interp = opts->interp;
+ ClockClientData *dataPtr = (ClockClientData *)opts->clientData;
+
+ int ret = TCL_ERROR;
+
+ /*
+ * Parse the date. The parser will fill a structure "info" with date,
+ * time, time zone, relative month/day/seconds, relative weekday, ordinal
+ * month.
+ * Notice that many yy-defines point to values in the "info" or "date"
+ * structure, e. g. yySecondOfDay -> info->date.secondOfDay or
+ * yyMonth -> info->date.month (same as yydate.month)
+ */
+ yyInput = Tcl_GetString(strObj);
+
+ if (TclClockFreeScan(interp, info) != TCL_OK) {
+ Tcl_Obj *msg;
+ TclNewObj(msg);
+ Tcl_AppendPrintfToObj(msg, "unable to convert date-time string \"%s\": %s",
+ Tcl_GetString(strObj), TclGetString(Tcl_GetObjResult(interp)));
+ Tcl_SetObjResult(interp, msg);
+ goto done;
+ }
+
+ /*
+ * If the caller supplied a date in the string, update the date with
+ * the value. If the caller didn't specify a time with the date, default to
+ * midnight.
+ */
+
+ if (info->flags & CLF_YEAR) {
+ if (yyYear < 100) {
+ if (yyYear >= dataPtr->yearOfCenturySwitch) {
+ yyYear -= 100;
+ }
+ yyYear += dataPtr->currentYearCentury;
+ }
+ yydate.isBce = 0;
+ info->flags |= CLF_ASSEMBLE_JULIANDAY|CLF_ASSEMBLE_SECONDS;
+ }
+
+ /*
+ * If the caller supplied a time zone in the string, make it into a time
+ * zone indicator of +-hhmm and setup this time zone.
+ */
+
+ if (info->flags & CLF_ZONE) {
+ if (yyTimezone || !yyDSTmode) {
+ /* Real time zone from numeric zone */
+ Tcl_Obj *tzObjStor = NULL;
+ int minEast = -yyTimezone;
+ int dstFlag = 1 - yyDSTmode;
+ tzObjStor = ClockFormatNumericTimeZone(
+ 60 * minEast + 3600 * dstFlag);
+ Tcl_IncrRefCount(tzObjStor);
+
+ opts->timezoneObj = ClockSetupTimeZone(dataPtr, interp, tzObjStor);
+
+ Tcl_DecrRefCount(tzObjStor);
+ } else {
+ /* simplest case - GMT / UTC */
+ opts->timezoneObj = ClockSetupTimeZone(dataPtr, interp,
+ dataPtr->literals[LIT_GMT]);
+ }
+ if (opts->timezoneObj == NULL) {
+ goto done;
+ }
+
+ // Tcl_SetObjRef(yydate.tzName, opts->timezoneObj);
+
+ info->flags |= CLF_ASSEMBLE_SECONDS;
+ }
+
+ /*
+ * For freescan apply validation rules (stage 1) before mixed with
+ * relative time (otherwise always valid recalculated date & time).
+ */
+ if ( (opts->flags & CLF_VALIDATE) ) {
+ if (ClockValidDate(info, opts, CLF_VALIDATE_S1) != TCL_OK) {
+ goto done;
+ }
+ }
+
+ /*
+ * Assemble date, time, zone into seconds-from-epoch
+ */
+
+ if ((info->flags & (CLF_TIME|CLF_HAVEDATE)) == CLF_HAVEDATE) {
+ yySecondOfDay = 0;
+ info->flags |= CLF_ASSEMBLE_SECONDS;
+ }
+ else
+ if (info->flags & CLF_TIME) {
+ yySecondOfDay = ToSeconds(yyHour, yyMinutes,
+ yySeconds, yyMeridian);
+ info->flags |= CLF_ASSEMBLE_SECONDS;
+ }
+ else
+ if ( (info->flags & (CLF_DAYOFWEEK|CLF_HAVEDATE)) == CLF_DAYOFWEEK
+ || (info->flags & CLF_ORDINALMONTH)
+ || ( (info->flags & CLF_RELCONV)
+ && ( yyRelMonth != 0
+ || yyRelDay != 0 ) )
+ ) {
+ yySecondOfDay = 0;
+ info->flags |= CLF_ASSEMBLE_SECONDS;
+ }
+ else {
+ yySecondOfDay = yydate.localSeconds % SECONDS_PER_DAY;
+ }
+
+ /*
+ * Do relative times
+ */
+
+ ret = ClockCalcRelTime(info);
+
+ /* Free scanning completed - date ready */
+
+done:
+
+ return ret;
+}
+
+/*----------------------------------------------------------------------
+ *
+ * ClockCalcRelTime --
+ *
+ * Used for calculating of relative times.
+ *
+ * Results:
+ * Returns a standard Tcl result.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+int
+ClockCalcRelTime(
+ DateInfo *info) /* Date fields used for converting */
+{
+
+ int prevDayOfWeek = yyDayOfWeek; /* preserve unchanged day of week */
+
+ /*
+ * Because some calculations require in-between conversion of the
+ * julian day, we can repeat this processing multiple times
+ */
+repeat_rel:
+
+ if (info->flags & CLF_RELCONV) {
+
+ /*
+ * Relative conversion normally possible in UTC time only, because
+ * of possible wrong local time increment if ignores in-between DST-hole.
+ * (see test-cases clock-34.53, clock-34.54).
+ * So increment date in julianDay, but time inside day in UTC (seconds).
+ */
+
+ /* add months (or years in months) */
+
+ if (yyRelMonth != 0) {
+ int m, h;
+
+ /* if needed extract year, month, etc. again */
+ if (info->flags & CLF_ASSEMBLE_DATE) {
+ GetGregorianEraYearDay(&yydate, GREGORIAN_CHANGE_DATE);
+ GetMonthDay(&yydate);
+ GetYearWeekDay(&yydate, GREGORIAN_CHANGE_DATE);
+ info->flags &= ~CLF_ASSEMBLE_DATE;
+ }
+
+ /* add the requisite number of months */
+ yyMonth += yyRelMonth - 1;
+ yyYear += yyMonth / 12;
+ m = yyMonth % 12;
+ /* compiler fix for negative offs - wrap y, m = (0, -1) -> (-1, 11) */
+ if (m < 0) {
+ yyYear--;
+ m = 12 + m;
+ }
+ yyMonth = m + 1;
+
+ /* if the day doesn't exist in the current month, repair it */
+ h = hath[IsGregorianLeapYear(&yydate)][m];
+ if (yyDay > h) {
+ yyDay = h;
+ }
+
+ /* on demand (lazy) assemble julianDay using new year, month, etc. */
+ info->flags |= CLF_ASSEMBLE_JULIANDAY|CLF_ASSEMBLE_SECONDS;
+
+ yyRelMonth = 0;
+ }
+
+ /* add days (or other parts aligned to days) */
+ if (yyRelDay) {
+
+ /* assemble julianDay using new year, month, etc. */
+ if (info->flags & CLF_ASSEMBLE_JULIANDAY) {
+ GetJulianDayFromEraYearMonthDay(&yydate, GREGORIAN_CHANGE_DATE);
+ info->flags &= ~CLF_ASSEMBLE_JULIANDAY;
+ }
+ yydate.julianDay += yyRelDay;
+
+ /* julianDay was changed, on demand (lazy) extract year, month, etc. again */
+ info->flags |= CLF_ASSEMBLE_DATE|CLF_ASSEMBLE_SECONDS;
+
+ yyRelDay = 0;
+ }
+
+ /* relative time (seconds), if exceeds current date, do the day conversion and
+ * leave rest of the increment in yyRelSeconds to add it hereafter in UTC seconds */
+ if (yyRelSeconds) {
+ Tcl_WideInt newSecs = yySecondOfDay + yyRelSeconds;
+
+ /* if seconds increment outside of current date, increment day */
+ if (newSecs / SECONDS_PER_DAY != yySecondOfDay / SECONDS_PER_DAY) {
+
+ yyRelDay += newSecs / SECONDS_PER_DAY;
+ yySecondOfDay = 0;
+ yyRelSeconds = newSecs % SECONDS_PER_DAY;
+
+ goto repeat_rel;
+ }
+ }
+
+ info->flags &= ~CLF_RELCONV;
}
/*
- * Return options as a list.
+ * Do relative (ordinal) month
*/
- Tcl_SetObjResult(interp, Tcl_NewListObj(3, results));
+ if (info->flags & CLF_ORDINALMONTH) {
+ int monthDiff;
+
+ /* if needed extract year, month, etc. again */
+ if (info->flags & CLF_ASSEMBLE_DATE) {
+ GetGregorianEraYearDay(&yydate, GREGORIAN_CHANGE_DATE);
+ GetMonthDay(&yydate);
+ GetYearWeekDay(&yydate, GREGORIAN_CHANGE_DATE);
+ info->flags &= ~CLF_ASSEMBLE_DATE;
+ }
+
+ if (yyMonthOrdinalIncr > 0) {
+ monthDiff = yyMonthOrdinal - yyMonth;
+ if (monthDiff <= 0) {
+ monthDiff += 12;
+ }
+ yyMonthOrdinalIncr--;
+ } else {
+ monthDiff = yyMonth - yyMonthOrdinal;
+ if (monthDiff >= 0) {
+ monthDiff -= 12;
+ }
+ yyMonthOrdinalIncr++;
+ }
+
+ /* process it further via relative times */
+ yyYear += yyMonthOrdinalIncr;
+ yyRelMonth += monthDiff;
+ info->flags &= ~CLF_ORDINALMONTH;
+ info->flags |= CLF_RELCONV|CLF_ASSEMBLE_JULIANDAY|CLF_ASSEMBLE_SECONDS;
+
+ goto repeat_rel;
+ }
+
+ /*
+ * Do relative weekday
+ */
+
+ if ((info->flags & (CLF_DAYOFWEEK|CLF_HAVEDATE)) == CLF_DAYOFWEEK) {
+
+ /* restore scanned day of week */
+ yyDayOfWeek = prevDayOfWeek;
+
+ /* if needed assemble julianDay now */
+ if (info->flags & CLF_ASSEMBLE_JULIANDAY) {
+ GetJulianDayFromEraYearMonthDay(&yydate, GREGORIAN_CHANGE_DATE);
+ info->flags &= ~CLF_ASSEMBLE_JULIANDAY;
+ }
+
+ yydate.isBce = 0;
+ yydate.julianDay = WeekdayOnOrBefore(yyDayOfWeek, yydate.julianDay + 6)
+ + 7 * yyDayOrdinal;
+ if (yyDayOrdinal > 0) {
+ yydate.julianDay -= 7;
+ }
+ info->flags |= CLF_ASSEMBLE_DATE|CLF_ASSEMBLE_SECONDS;
+ }
+
return TCL_OK;
+}
-#undef timezoneObj
-#undef localeObj
-#undef formatObj
+
+/*----------------------------------------------------------------------
+ *
+ * ClockWeekdaysOffs --
+ *
+ * Get offset in days for the number of week days corresponding the
+ * given day of week (skipping Saturdays and Sundays).
+ *
+ *
+ * Results:
+ * Returns a day increment adjusted the given weekdays
+ *
+ *----------------------------------------------------------------------
+ */
+
+static inline int
+ClockWeekdaysOffs(
+ int dayOfWeek,
+ int offs)
+{
+ int weeks, resDayOfWeek;
+
+ /* offset in days */
+ weeks = offs / 5;
+ offs = offs % 5;
+ /* compiler fix for negative offs - wrap (0, -1) -> (-1, 4) */
+ if (offs < 0) {
+ weeks--;
+ offs = 5 + offs;
+ }
+ offs += 7 * weeks;
+
+ /* resulting day of week */
+ {
+ int day = (offs % 7);
+ /* compiler fix for negative offs - wrap (0, -1) -> (-1, 6) */
+ if (day < 0) {
+ day = 7 + day;
+ }
+ resDayOfWeek = dayOfWeek + day;
+ }
+
+ /* adjust if we start from a weekend */
+ if (dayOfWeek > 5) {
+ int adj = 5 - dayOfWeek;
+ offs += adj;
+ resDayOfWeek += adj;
+ }
+
+ /* adjust if we end up on a weekend */
+ if (resDayOfWeek > 5) {
+ offs += 2;
+ }
+
+ return offs;
+}
+
+
+
+/*----------------------------------------------------------------------
+ *
+ * ClockAddObjCmd -- , clock add --
+ *
+ * Adds an offset to a given time.
+ *
+ * Refer to the user documentation to see what it exactly does.
+ *
+ * Syntax:
+ * clock add clockval ?count unit?... ?-option value?
+ *
+ * Parameters:
+ * clockval -- Starting time value
+ * count -- Amount of a unit of time to add
+ * unit -- Unit of time to add, must be one of:
+ * years year months month weeks week
+ * days day hours hour minutes minute
+ * seconds second
+ *
+ * Options:
+ * -gmt BOOLEAN
+ * Flag synonymous with '-timezone :GMT'
+ * -timezone ZONE
+ * Name of the time zone in which calculations are to be done.
+ * -locale NAME
+ * Name of the locale in which calculations are to be done.
+ * Used to determine the Gregorian change date.
+ *
+ * Results:
+ * Returns a standard Tcl result with the given time adjusted
+ * by the given offset(s) in order.
+ *
+ * Notes:
+ * It is possible that adding a number of months or years will adjust the
+ * day of the month as well. For instance, the time at one month after
+ * 31 January is either 28 or 29 February, because February has fewer
+ * than 31 days.
+ *
+ *----------------------------------------------------------------------
+ */
+
+int
+ClockAddObjCmd(
+ void *clientData, /* Client data containing literal pool */
+ Tcl_Interp *interp, /* Tcl interpreter */
+ int objc, /* Parameter count */
+ Tcl_Obj *const objv[]) /* Parameter values */
+{
+ static const char *syntax = "clock add clockval|-now ?number units?..."
+ "?-gmt boolean? "
+ "?-locale LOCALE? ?-timezone ZONE?";
+ ClockClientData *dataPtr = (ClockClientData *)clientData;
+ int ret;
+ ClockFmtScnCmdArgs opts; /* Format, locale, timezone and base */
+ DateInfo yy; /* Common structure used for parsing */
+ DateInfo *info = &yy;
+
+ /* add "week" to units also (because otherwise ambiguous) */
+ static const char *const units[] = {
+ "years", "months", "week", "weeks",
+ "days", "weekdays",
+ "hours", "minutes", "seconds",
+ NULL
+ };
+ enum unitInd {
+ CLC_ADD_YEARS, CLC_ADD_MONTHS, CLC_ADD_WEEK, CLC_ADD_WEEKS,
+ CLC_ADD_DAYS, CLC_ADD_WEEKDAYS,
+ CLC_ADD_HOURS, CLC_ADD_MINUTES, CLC_ADD_SECONDS
+ };
+ int unitIndex; /* Index of an option. */
+ int i;
+ Tcl_WideInt offs;
+
+ /* even number of arguments */
+ if ((objc & 1) == 1) {
+ Tcl_WrongNumArgs(interp, 0, objv, syntax);
+ Tcl_SetErrorCode(interp, "CLOCK", "wrongNumArgs", (char *)NULL);
+ return TCL_ERROR;
+ }
+
+ ClockInitDateInfo(&yy);
+
+ /*
+ * Extract values for the keywords.
+ */
+
+ ClockInitFmtScnArgs(clientData, interp, &opts);
+ ret = ClockParseFmtScnArgs(&opts, &yy.date, objc, objv,
+ CLC_ADD_ARGS, "-gmt, -locale, or -timezone");
+ if (ret != TCL_OK) {
+ goto done;
+ }
+
+ /* time together as seconds of the day */
+ yySecondOfDay = yySeconds = yydate.localSeconds % SECONDS_PER_DAY;
+ /* seconds are in localSeconds (relative base date), so reset time here */
+ yyHour = 0; yyMinutes = 0; yyMeridian = MER24;
+
+ ret = TCL_ERROR;
+
+ /*
+ * Find each offset and process date increment
+ */
+
+ for (i = 2; i < objc; i+=2) {
+ /* bypass not integers (options, allready processed above in ClockParseFmtScnArgs) */
+ if (TclGetWideIntFromObj(NULL, objv[i], &offs) != TCL_OK) {
+ continue;
+ }
+ /* get unit */
+ if (Tcl_GetIndexFromObj(interp, objv[i+1], units, "unit", 0,
+ &unitIndex) != TCL_OK) {
+ goto done;
+ }
+ if (objv[i]->typePtr == &tclBignumType
+ || offs > (unitIndex < CLC_ADD_HOURS ? 0x7fffffff : TCL_MAX_SECONDS)
+ || offs < (unitIndex < CLC_ADD_HOURS ? -0x7fffffff : TCL_MIN_SECONDS)
+ ) {
+ Tcl_SetObjResult(interp, dataPtr->literals[LIT_INTEGER_VALUE_TOO_LARGE]);
+ goto done;
+ }
+
+ /* nothing to do if zero quantity */
+ if (!offs) {
+ continue;
+ }
+
+ /* if in-between conversion needed (already have relative date/time),
+ * correct date info, because the date may be changed,
+ * so refresh it now */
+
+ if ( (info->flags & CLF_RELCONV)
+ && ( unitIndex == CLC_ADD_WEEKDAYS
+ /* some months can be shorter as another */
+ || yyRelMonth || yyRelDay
+ /* day changed */
+ || yySeconds + yyRelSeconds > SECONDS_PER_DAY
+ || yySeconds + yyRelSeconds < 0
+ )
+ ) {
+ if (ClockCalcRelTime(info) != TCL_OK) {
+ goto done;
+ }
+ }
+
+ /* process increment by offset + unit */
+ info->flags |= CLF_RELCONV;
+ switch (unitIndex) {
+ case CLC_ADD_YEARS:
+ yyRelMonth += offs * 12;
+ break;
+ case CLC_ADD_MONTHS:
+ yyRelMonth += offs;
+ break;
+ case CLC_ADD_WEEK:
+ case CLC_ADD_WEEKS:
+ yyRelDay += offs * 7;
+ break;
+ case CLC_ADD_DAYS:
+ yyRelDay += offs;
+ break;
+ case CLC_ADD_WEEKDAYS:
+ /* add number of week days (skipping Saturdays and Sundays)
+ * to a relative days value. */
+ offs = ClockWeekdaysOffs(yy.date.dayOfWeek, offs);
+ yyRelDay += offs;
+ break;
+ case CLC_ADD_HOURS:
+ yyRelSeconds += offs * 60 * 60;
+ break;
+ case CLC_ADD_MINUTES:
+ yyRelSeconds += offs * 60;
+ break;
+ case CLC_ADD_SECONDS:
+ yyRelSeconds += offs;
+ break;
+ }
+ }
+
+ /*
+ * Do relative times (if not yet already processed interim):
+ */
+
+ if (info->flags & CLF_RELCONV) {
+ if (ClockCalcRelTime(info) != TCL_OK) {
+ goto done;
+ }
+ }
+
+ /* Convert date info structure into UTC seconds */
+
+ ret = ClockScanCommit(&yy, &opts);
+
+done:
+
+ Tcl_UnsetObjRef(yy.date.tzName);
+
+ if (ret != TCL_OK) {
+ return ret;
+ }
+
+ Tcl_SetObjResult(interp, Tcl_NewWideIntObj(yy.date.seconds));
+ return TCL_OK;
}
/*----------------------------------------------------------------------
@@ -2011,7 +4581,7 @@ ClockSecondsObjCmd(
Tcl_Obj *timeObj;
if (objc != 1) {
- Tcl_WrongNumArgs(interp, 1, objv, NULL);
+ Tcl_WrongNumArgs(interp, 0, objv, "clock seconds");
return TCL_ERROR;
}
Tcl_GetTime(&now);
@@ -2024,6 +4594,76 @@ ClockSecondsObjCmd(
/*
*----------------------------------------------------------------------
*
+ * ClockSafeCatchCmd --
+ *
+ * Same as "::catch" command but avoids overwriting of interp state.
+ *
+ * See [554117edde] for more info (and proper solution).
+ *
+ *----------------------------------------------------------------------
+ */
+int
+ClockSafeCatchCmd(
+ TCL_UNUSED(void *),
+ Tcl_Interp *interp,
+ int objc,
+ Tcl_Obj *const objv[])
+{
+ typedef struct {
+ int status; /* return code status */
+ int flags; /* Each remaining field saves the */
+ int returnLevel; /* corresponding field of the Interp */
+ int returnCode; /* struct. These fields taken together are */
+ Tcl_Obj *errorInfo; /* the "state" of the interp. */
+ Tcl_Obj *errorCode;
+ Tcl_Obj *returnOpts;
+ Tcl_Obj *objResult;
+ Tcl_Obj *errorStack;
+ int resetErrorStack;
+ } InterpState;
+
+ Interp *iPtr = (Interp *)interp;
+ int ret, flags = 0;
+ InterpState *statePtr;
+
+ if (objc == 1) {
+ /* wrong # args : */
+ return Tcl_CatchObjCmd(NULL, interp, objc, objv);
+ }
+
+ statePtr = (InterpState *)Tcl_SaveInterpState(interp, 0);
+ if (!statePtr->errorInfo) {
+ /* todo: avoid traced get of errorInfo here */
+ Tcl_InitObjRef(statePtr->errorInfo,
+ Tcl_ObjGetVar2(interp, iPtr->eiVar, NULL, 0));
+ flags |= ERR_LEGACY_COPY;
+ }
+ if (!statePtr->errorCode) {
+ /* todo: avoid traced get of errorCode here */
+ Tcl_InitObjRef(statePtr->errorCode,
+ Tcl_ObjGetVar2(interp, iPtr->ecVar, NULL, 0));
+ flags |= ERR_LEGACY_COPY;
+ }
+
+ /* original catch */
+ ret = Tcl_CatchObjCmd(NULL, interp, objc, objv);
+
+ if (ret == TCL_ERROR) {
+ Tcl_DiscardInterpState((Tcl_InterpState)statePtr);
+ return TCL_ERROR;
+ }
+ /* overwrite result in state with catch result */
+ Tcl_SetObjRef(statePtr->objResult, Tcl_GetObjResult(interp));
+ /* set result (together with restore state) to interpreter */
+ (void) Tcl_RestoreInterpState(interp, (Tcl_InterpState)statePtr);
+ /* todo: unless ERR_LEGACY_COPY not set in restore (branch [bug-554117edde] not merged yet) */
+ iPtr->flags |= (flags & ERR_LEGACY_COPY);
+ return ret;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
* TzsetIfNecessary --
*
* Calls the tzset() library function if the contents of the TZ
@@ -2047,12 +4687,13 @@ ClockSecondsObjCmd(
#define wcscpy strcpy
#endif
-static void
+static size_t
TzsetIfNecessary(void)
{
static WCHAR* tzWas = (WCHAR *)INT2PTR(-1); /* Previous value of TZ, protected by
* clockMutex. */
static long long tzLastRefresh = 0; /* Used for latency before next refresh */
+ static size_t tzWasEpoch = 0; /* Epoch, signals that TZ changed */
static size_t tzEnvEpoch = 0; /* Last env epoch, for faster signaling,
that TZ changed via TCL */
const WCHAR *tzIsNow; /* Current value of TZ */
@@ -2065,14 +4706,18 @@ TzsetIfNecessary(void)
Tcl_Time now;
Tcl_GetTime(&now);
if (now.sec == tzLastRefresh && tzEnvEpoch == TclEnvEpoch) {
- return;
+ return tzWasEpoch;
}
tzEnvEpoch = TclEnvEpoch;
tzLastRefresh = now.sec;
+ /* check in lock */
Tcl_MutexLock(&clockMutex);
- tzIsNow = getenv("TZ");
+ tzIsNow = getenv("TCL_TZ");
+ if (tzIsNow == NULL) {
+ tzIsNow = getenv("TZ");
+ }
if (tzIsNow != NULL && (tzWas == NULL || tzWas == (WCHAR *)INT2PTR(-1)
|| wcscmp(tzIsNow, tzWas) != 0)) {
tzset();
@@ -2081,44 +4726,18 @@ TzsetIfNecessary(void)
}
tzWas = (WCHAR *)Tcl_Alloc(sizeof(WCHAR) * (wcslen(tzIsNow) + 1));
wcscpy(tzWas, tzIsNow);
+ tzWasEpoch++;
} else if (tzIsNow == NULL && tzWas != NULL) {
tzset();
if (tzWas != (WCHAR *)INT2PTR(-1)) {
Tcl_Free(tzWas);
}
tzWas = NULL;
+ tzWasEpoch++;
}
Tcl_MutexUnlock(&clockMutex);
-}
-
-/*
- *----------------------------------------------------------------------
- *
- * ClockDeleteCmdProc --
- *
- * Remove a reference to the clock client data, and clean up memory
- * when it's all gone.
- *
- * Results:
- * None.
- *
- *----------------------------------------------------------------------
- */
-
-static void
-ClockDeleteCmdProc(
- void *clientData) /* Opaque pointer to the client data */
-{
- ClockClientData *data = (ClockClientData *)clientData;
- int i;
- if (data->refCount-- <= 1) {
- for (i = 0; i < LIT__END; ++i) {
- Tcl_DecrRefCount(data->literals[i]);
- }
- Tcl_Free(data->literals);
- Tcl_Free(data);
- }
+ return tzWasEpoch;
}
/*
diff --git a/generic/tclClockFmt.c b/generic/tclClockFmt.c
new file mode 100644
index 0000000..d2175e6
--- /dev/null
+++ b/generic/tclClockFmt.c
@@ -0,0 +1,3391 @@
+/*
+ * tclClockFmt.c --
+ *
+ * Contains the date format (and scan) routines. This code is back-ported
+ * from the time and date facilities of tclSE engine, by Serg G. Brester.
+ *
+ * Copyright (c) 2015 by Sergey G. Brester aka sebres. All rights reserved.
+ *
+ * See the file "license.terms" for information on usage and redistribution of
+ * this file, and for a DISCLAIMER OF ALL WARRANTIES.
+ */
+
+#include "tclInt.h"
+#include "tclStrIdxTree.h"
+#include "tclDate.h"
+
+/*
+ * Miscellaneous forward declarations and functions used within this file
+ */
+
+static void
+ClockFmtObj_DupInternalRep(Tcl_Obj *srcPtr, Tcl_Obj *copyPtr);
+static void
+ClockFmtObj_FreeInternalRep(Tcl_Obj *objPtr);
+static int
+ClockFmtObj_SetFromAny(Tcl_Interp *interp, Tcl_Obj *objPtr);
+static void
+ClockFmtObj_UpdateString(Tcl_Obj *objPtr);
+
+
+TCL_DECLARE_MUTEX(ClockFmtMutex); /* Serializes access to common format list. */
+
+static void ClockFmtScnStorageDelete(ClockFmtScnStorage *fss);
+
+static void ClockFrmScnFinalize(void *clientData);
+
+/*
+ * Clock scan and format facilities.
+ */
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * _str2int -- , _str2wideInt --
+ *
+ * Fast inline-convertion of string to signed int or wide int by given
+ * start/end.
+ *
+ * The given string should contain numbers chars only (because already
+ * pre-validated within parsing routines)
+ *
+ * Results:
+ * Returns a standard Tcl result.
+ * TCL_OK - by successful conversion, TCL_ERROR by (wide) int overflow
+ *
+ *----------------------------------------------------------------------
+ */
+
+/* int overflows may happens here (expected case) */
+#if defined(__GNUC__) || defined(__GNUG__)
+# pragma GCC optimize("no-trapv")
+#endif
+
+static inline int
+_str2int(
+ int *out,
+ const char *p,
+ const char *e,
+ int sign)
+{
+ int val = 0, prev = 0;
+ if (sign >= 0) {
+ while (p < e) {
+ val = val * 10 + (*p++ - '0');
+ if (val < prev) {
+ return TCL_ERROR;
+ }
+ prev = val;
+ }
+ } else {
+ while (p < e) {
+ val = val * 10 - (*p++ - '0');
+ if (val > prev) {
+ return TCL_ERROR;
+ }
+ prev = val;
+ }
+ }
+ *out = val;
+ return TCL_OK;
+}
+
+static inline int
+_str2wideInt(
+ Tcl_WideInt *out,
+ const char *p,
+ const char *e,
+ int sign)
+{
+ Tcl_WideInt val = 0, prev = 0;
+ if (sign >= 0) {
+ while (p < e) {
+ val = val * 10 + (*p++ - '0');
+ if (val < prev) {
+ return TCL_ERROR;
+ }
+ prev = val;
+ }
+ } else {
+ while (p < e) {
+ val = val * 10 - (*p++ - '0');
+ if (val > prev) {
+ return TCL_ERROR;
+ }
+ prev = val;
+ }
+ }
+ *out = val;
+ return TCL_OK;
+}
+
+#if defined(__GNUC__) || defined(__GNUG__)
+# pragma GCC reset_options
+#endif
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * _itoaw -- , _witoaw --
+ *
+ * Fast inline-convertion of signed int or wide int to string, using
+ * given padding with specified padchar and width (or without padding).
+ *
+ * This is a very fast replacement for sprintf("%02d").
+ *
+ * Results:
+ * Returns position in buffer after end of conversion result.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static inline char *
+_itoaw(
+ char *buf,
+ int val,
+ char padchar,
+ unsigned short int width)
+{
+ char *p;
+ static int wrange[] = {1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000};
+
+ /* positive integer */
+
+ if (val >= 0)
+ {
+ /* check resp. recalculate width */
+ while (width <= 9 && val >= wrange[width]) {
+ width++;
+ }
+ /* number to string backwards */
+ p = buf + width;
+ *p-- = '\0';
+ do {
+ char c = (val % 10); val /= 10;
+ *p-- = '0' + c;
+ } while (val > 0);
+ /* fulling with pad-char */
+ while (p >= buf) {
+ *p-- = padchar;
+ }
+
+ return buf + width;
+ }
+ /* negative integer */
+
+ if (!width) width++;
+ /* check resp. recalculate width (regarding sign) */
+ width--;
+ while (width <= 9 && val <= -wrange[width]) {
+ width++;
+ }
+ width++;
+ /* number to string backwards */
+ p = buf + width;
+ *p-- = '\0';
+ /* differentiate platforms with -1 % 10 == 1 and -1 % 10 == -1 */
+ if (-1 % 10 == -1) {
+ do {
+ char c = (val % 10); val /= 10;
+ *p-- = '0' - c;
+ } while (val < 0);
+ } else {
+ do {
+ char c = (val % 10); val /= 10;
+ *p-- = '0' + c;
+ } while (val < 0);
+ }
+ /* sign by 0 padding */
+ if (padchar != '0') { *p-- = '-'; }
+ /* fulling with pad-char */
+ while (p >= buf + 1) {
+ *p-- = padchar;
+ }
+ /* sign by non 0 padding */
+ if (padchar == '0') { *p = '-'; }
+
+ return buf + width;
+}
+char *
+TclItoAw(
+ char *buf,
+ int val,
+ char padchar,
+ unsigned short int width)
+{
+ return _itoaw(buf, val, padchar, width);
+}
+
+static inline char *
+_witoaw(
+ char *buf,
+ Tcl_WideInt val,
+ char padchar,
+ unsigned short int width)
+{
+ char *p;
+ static int wrange[] = {1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000};
+
+ /* positive integer */
+
+ if (val >= 0)
+ {
+ /* check resp. recalculate width */
+ if (val >= 10000000000LL) {
+ Tcl_WideInt val2;
+ val2 = val / 10000000000LL;
+ while (width <= 9 && val2 >= wrange[width]) {
+ width++;
+ }
+ width += 10;
+ } else {
+ while (width <= 9 && val >= wrange[width]) {
+ width++;
+ }
+ }
+ /* number to string backwards */
+ p = buf + width;
+ *p-- = '\0';
+ do {
+ char c = (val % 10); val /= 10;
+ *p-- = '0' + c;
+ } while (val > 0);
+ /* fulling with pad-char */
+ while (p >= buf) {
+ *p-- = padchar;
+ }
+
+ return buf + width;
+ }
+
+ /* negative integer */
+
+ if (!width) width++;
+ /* check resp. recalculate width (regarding sign) */
+ width--;
+ if (val <= -10000000000LL) {
+ Tcl_WideInt val2;
+ val2 = val / 10000000000LL;
+ while (width <= 9 && val2 <= -wrange[width]) {
+ width++;
+ }
+ width += 10;
+ } else {
+ while (width <= 9 && val <= -wrange[width]) {
+ width++;
+ }
+ }
+ width++;
+ /* number to string backwards */
+ p = buf + width;
+ *p-- = '\0';
+ /* differentiate platforms with -1 % 10 == 1 and -1 % 10 == -1 */
+ if (-1 % 10 == -1) {
+ do {
+ char c = (val % 10); val /= 10;
+ *p-- = '0' - c;
+ } while (val < 0);
+ } else {
+ do {
+ char c = (val % 10); val /= 10;
+ *p-- = '0' + c;
+ } while (val < 0);
+ }
+ /* sign by 0 padding */
+ if (padchar != '0') { *p-- = '-'; }
+ /* fulling with pad-char */
+ while (p >= buf + 1) {
+ *p-- = padchar;
+ }
+ /* sign by non 0 padding */
+ if (padchar == '0') { *p = '-'; }
+
+ return buf + width;
+}
+
+/*
+ * Global GC as LIFO for released scan/format object storages.
+ *
+ * Used to holds last released CLOCK_FMT_SCN_STORAGE_GC_SIZE formats
+ * (after last reference from Tcl-object will be removed). This is helpful
+ * to avoid continuous (re)creation and compiling by some dynamically resp.
+ * variable format objects, that could be often reused.
+ *
+ * As long as format storage is used resp. belongs to GC, it takes place in
+ * FmtScnHashTable also.
+ */
+
+#if CLOCK_FMT_SCN_STORAGE_GC_SIZE > 0
+
+static struct {
+ ClockFmtScnStorage *stackPtr;
+ ClockFmtScnStorage *stackBound;
+ unsigned int count;
+} ClockFmtScnStorage_GC = {NULL, NULL, 0};
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * ClockFmtScnStorageGC_In --
+ *
+ * Adds an format storage object to GC.
+ *
+ * If current GC is full (size larger as CLOCK_FMT_SCN_STORAGE_GC_SIZE)
+ * this removes last unused storage at begin of GC stack (LIFO).
+ *
+ * Assumes caller holds the ClockFmtMutex.
+ *
+ * Results:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static inline void
+ClockFmtScnStorageGC_In(ClockFmtScnStorage *entry)
+{
+ /* add new entry */
+ TclSpliceIn(entry, ClockFmtScnStorage_GC.stackPtr);
+ if (ClockFmtScnStorage_GC.stackBound == NULL) {
+ ClockFmtScnStorage_GC.stackBound = entry;
+ }
+ ClockFmtScnStorage_GC.count++;
+
+ /* if GC ist full */
+ if (ClockFmtScnStorage_GC.count > CLOCK_FMT_SCN_STORAGE_GC_SIZE) {
+
+ /* GC stack is LIFO: delete first inserted entry */
+ ClockFmtScnStorage *delEnt = ClockFmtScnStorage_GC.stackBound;
+ ClockFmtScnStorage_GC.stackBound = delEnt->prevPtr;
+ TclSpliceOut(delEnt, ClockFmtScnStorage_GC.stackPtr);
+ ClockFmtScnStorage_GC.count--;
+ delEnt->prevPtr = delEnt->nextPtr = NULL;
+ /* remove it now */
+ ClockFmtScnStorageDelete(delEnt);
+ }
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * ClockFmtScnStorage_GC_Out --
+ *
+ * Restores (for reusing) given format storage object from GC.
+ *
+ * Assumes caller holds the ClockFmtMutex.
+ *
+ * Results:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static inline void
+ClockFmtScnStorage_GC_Out(ClockFmtScnStorage *entry)
+{
+ TclSpliceOut(entry, ClockFmtScnStorage_GC.stackPtr);
+ ClockFmtScnStorage_GC.count--;
+ if (ClockFmtScnStorage_GC.stackBound == entry) {
+ ClockFmtScnStorage_GC.stackBound = entry->prevPtr;
+ }
+ entry->prevPtr = entry->nextPtr = NULL;
+}
+
+#endif
+
+
+/*
+ * Global format storage hash table of type ClockFmtScnStorageHashKeyType
+ * (contains list of scan/format object storages, shared across all threads).
+ *
+ * Used for fast searching by format string.
+ */
+static Tcl_HashTable FmtScnHashTable;
+static int initialized = 0;
+
+/*
+ * Wrappers between pointers to hash entry and format storage object
+ */
+static inline Tcl_HashEntry *
+HashEntry4FmtScn(ClockFmtScnStorage *fss) {
+ return (Tcl_HashEntry*)(fss + 1);
+};
+static inline ClockFmtScnStorage *
+FmtScn4HashEntry(Tcl_HashEntry *hKeyPtr) {
+ return (ClockFmtScnStorage*)(((char*)hKeyPtr) - sizeof(ClockFmtScnStorage));
+};
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * ClockFmtScnStorageAllocProc --
+ *
+ * Allocate space for a hash entry containing format storage together
+ * with the string key.
+ *
+ * Results:
+ * The return value is a pointer to the created entry.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static Tcl_HashEntry *
+ClockFmtScnStorageAllocProc(
+ TCL_UNUSED(Tcl_HashTable *), /* Hash table. */
+ void *keyPtr) /* Key to store in the hash table entry. */
+{
+ ClockFmtScnStorage *fss;
+ const char *string = (const char *) keyPtr;
+ Tcl_HashEntry *hPtr;
+ unsigned int size,
+ allocsize = sizeof(ClockFmtScnStorage) + sizeof(Tcl_HashEntry);
+
+ allocsize += (size = strlen(string) + 1);
+ if (size > sizeof(hPtr->key)) {
+ allocsize -= sizeof(hPtr->key);
+ }
+
+ fss = (ClockFmtScnStorage *)Tcl_Alloc(allocsize);
+
+ /* initialize */
+ memset(fss, 0, sizeof(*fss));
+
+ hPtr = HashEntry4FmtScn(fss);
+ memcpy(&hPtr->key.string, string, size);
+ hPtr->clientData = 0; /* currently unused */
+
+ return hPtr;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * ClockFmtScnStorageFreeProc --
+ *
+ * Free format storage object and space of given hash entry.
+ *
+ * Results:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+ClockFmtScnStorageFreeProc(
+ Tcl_HashEntry *hPtr)
+{
+ ClockFmtScnStorage *fss = FmtScn4HashEntry(hPtr);
+
+ if (fss->scnTok != NULL) {
+ Tcl_Free(fss->scnTok);
+ fss->scnTok = NULL;
+ fss->scnTokC = 0;
+ }
+ if (fss->fmtTok != NULL) {
+ Tcl_Free(fss->fmtTok);
+ fss->fmtTok = NULL;
+ fss->fmtTokC = 0;
+ }
+
+ Tcl_Free(fss);
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * ClockFmtScnStorageDelete --
+ *
+ * Delete format storage object.
+ *
+ * Results:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+ClockFmtScnStorageDelete(ClockFmtScnStorage *fss) {
+ Tcl_HashEntry *hPtr = HashEntry4FmtScn(fss);
+ /*
+ * This will delete a hash entry and call "Tcl_Free" for storage self, if
+ * some additionally handling required, freeEntryProc can be used instead
+ */
+ Tcl_DeleteHashEntry(hPtr);
+}
+
+
+/*
+ * Derivation of tclStringHashKeyType with another allocEntryProc
+ */
+
+static Tcl_HashKeyType ClockFmtScnStorageHashKeyType;
+
+
+/*
+ * Type definition of clock-format tcl object type.
+ */
+
+static const Tcl_ObjType ClockFmtObjType = {
+ "clock-format", /* name */
+ ClockFmtObj_FreeInternalRep, /* freeIntRepProc */
+ ClockFmtObj_DupInternalRep, /* dupIntRepProc */
+ ClockFmtObj_UpdateString, /* updateStringProc */
+ ClockFmtObj_SetFromAny, /* setFromAnyProc */
+ TCL_OBJTYPE_V0
+};
+
+#define ObjClockFmtScn(objPtr) \
+ (*((ClockFmtScnStorage **)&(objPtr)->internalRep.twoPtrValue.ptr1))
+
+#define ObjLocFmtKey(objPtr) \
+ (*((Tcl_Obj **)&(objPtr)->internalRep.twoPtrValue.ptr2))
+
+static void
+ClockFmtObj_DupInternalRep(
+ Tcl_Obj *srcPtr,
+ Tcl_Obj *copyPtr)
+{
+ ClockFmtScnStorage *fss = ObjClockFmtScn(srcPtr);
+
+ if (fss != NULL) {
+ Tcl_MutexLock(&ClockFmtMutex);
+ fss->objRefCount++;
+ Tcl_MutexUnlock(&ClockFmtMutex);
+ }
+
+ ObjClockFmtScn(copyPtr) = fss;
+ /* regards special case - format not localizable */
+ if (ObjLocFmtKey(srcPtr) != srcPtr) {
+ Tcl_InitObjRef(ObjLocFmtKey(copyPtr), ObjLocFmtKey(srcPtr));
+ } else {
+ ObjLocFmtKey(copyPtr) = copyPtr;
+ }
+ copyPtr->typePtr = &ClockFmtObjType;
+
+
+ /* if no format representation, dup string representation */
+ if (fss == NULL) {
+ copyPtr->bytes = (char *)Tcl_Alloc(srcPtr->length + 1);
+ memcpy(copyPtr->bytes, srcPtr->bytes, srcPtr->length + 1);
+ copyPtr->length = srcPtr->length;
+ }
+}
+
+static void
+ClockFmtObj_FreeInternalRep(
+ Tcl_Obj *objPtr)
+{
+ ClockFmtScnStorage *fss = ObjClockFmtScn(objPtr);
+ if (fss != NULL) {
+ Tcl_MutexLock(&ClockFmtMutex);
+ /* decrement object reference count of format/scan storage */
+ if (--fss->objRefCount <= 0) {
+ #if CLOCK_FMT_SCN_STORAGE_GC_SIZE > 0
+ /* don't remove it right now (may be reusable), just add to GC */
+ ClockFmtScnStorageGC_In(fss);
+ #else
+ /* remove storage (format representation) */
+ ClockFmtScnStorageDelete(fss);
+ #endif
+ }
+ Tcl_MutexUnlock(&ClockFmtMutex);
+ }
+ ObjClockFmtScn(objPtr) = NULL;
+ if (ObjLocFmtKey(objPtr) != objPtr) {
+ Tcl_UnsetObjRef(ObjLocFmtKey(objPtr));
+ } else {
+ ObjLocFmtKey(objPtr) = NULL;
+ }
+ objPtr->typePtr = NULL;
+};
+
+static int
+ClockFmtObj_SetFromAny(
+ TCL_UNUSED(Tcl_Interp *),
+ Tcl_Obj *objPtr)
+{
+ /* validate string representation before free old internal representation */
+ (void)TclGetString(objPtr);
+
+ /* free old internal representation */
+ if (objPtr->typePtr && objPtr->typePtr->freeIntRepProc)
+ objPtr->typePtr->freeIntRepProc(objPtr);
+
+ /* initial state of format object */
+ ObjClockFmtScn(objPtr) = NULL;
+ ObjLocFmtKey(objPtr) = NULL;
+ objPtr->typePtr = &ClockFmtObjType;
+
+ return TCL_OK;
+};
+
+static void
+ClockFmtObj_UpdateString(
+ Tcl_Obj *objPtr)
+{
+ const char *name = "UNKNOWN";
+ size_t len;
+ ClockFmtScnStorage *fss = ObjClockFmtScn(objPtr);
+
+ if (fss != NULL) {
+ Tcl_HashEntry *hPtr = HashEntry4FmtScn(fss);
+ name = hPtr->key.string;
+ }
+ len = strlen(name);
+ objPtr->length = len++,
+ objPtr->bytes = (char *)Tcl_Alloc(len);
+ if (objPtr->bytes) {
+ memcpy(objPtr->bytes, name, len);
+ }
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * ClockFrmObjGetLocFmtKey --
+ *
+ * Retrieves format key object used to search localized format.
+ *
+ * This is normally stored in second pointer of internal representation.
+ * If format object is not localizable, it is equal the given format
+ * pointer (special case to fast fallback by not-localizable formats).
+ *
+ * Results:
+ * Returns tcl object with key or format object if not localizable.
+ *
+ * Side effects:
+ * Converts given format object to ClockFmtObjType on demand for caching
+ * the key inside its internal representation.
+ *
+ *----------------------------------------------------------------------
+ */
+
+Tcl_Obj*
+ClockFrmObjGetLocFmtKey(
+ Tcl_Interp *interp,
+ Tcl_Obj *objPtr)
+{
+ Tcl_Obj *keyObj;
+
+ if (objPtr->typePtr != &ClockFmtObjType) {
+ if (ClockFmtObj_SetFromAny(interp, objPtr) != TCL_OK) {
+ return NULL;
+ }
+ }
+
+ keyObj = ObjLocFmtKey(objPtr);
+ if (keyObj) {
+ return keyObj;
+ }
+
+ keyObj = Tcl_ObjPrintf("FMT_%s", TclGetString(objPtr));
+ Tcl_InitObjRef(ObjLocFmtKey(objPtr), keyObj);
+
+ return keyObj;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * FindOrCreateFmtScnStorage --
+ *
+ * Retrieves format storage for given string format.
+ *
+ * This will find the given format in the global storage hash table
+ * or create a format storage object on demaind and save the
+ * reference in the first pointer of internal representation of given
+ * object.
+ *
+ * Results:
+ * Returns scan/format storage pointer to ClockFmtScnStorage.
+ *
+ * Side effects:
+ * Converts given format object to ClockFmtObjType on demand for caching
+ * the format storage reference inside its internal representation.
+ * Increments objRefCount of the ClockFmtScnStorage reference.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static ClockFmtScnStorage *
+FindOrCreateFmtScnStorage(
+ Tcl_Interp *interp,
+ Tcl_Obj *objPtr)
+{
+ const char *strFmt = TclGetString(objPtr);
+ ClockFmtScnStorage *fss = NULL;
+ int isNew;
+ Tcl_HashEntry *hPtr;
+
+ Tcl_MutexLock(&ClockFmtMutex);
+
+ /* if not yet initialized */
+ if (!initialized) {
+ /* initialize type */
+ memcpy(&ClockFmtScnStorageHashKeyType, &tclStringHashKeyType, sizeof(tclStringHashKeyType));
+ ClockFmtScnStorageHashKeyType.allocEntryProc = ClockFmtScnStorageAllocProc;
+ ClockFmtScnStorageHashKeyType.freeEntryProc = ClockFmtScnStorageFreeProc;
+
+ /* initialize hash table */
+ Tcl_InitCustomHashTable(&FmtScnHashTable, TCL_CUSTOM_TYPE_KEYS,
+ &ClockFmtScnStorageHashKeyType);
+
+ initialized = 1;
+ Tcl_CreateExitHandler(ClockFrmScnFinalize, NULL);
+ }
+
+ /* get or create entry (and alocate storage) */
+ hPtr = Tcl_CreateHashEntry(&FmtScnHashTable, strFmt, &isNew);
+ if (hPtr != NULL) {
+
+ fss = FmtScn4HashEntry(hPtr);
+
+ #if CLOCK_FMT_SCN_STORAGE_GC_SIZE > 0
+ /* unlink if it is currently in GC */
+ if (isNew == 0 && fss->objRefCount == 0) {
+ ClockFmtScnStorage_GC_Out(fss);
+ }
+ #endif
+
+ /* new reference, so increment in lock right now */
+ fss->objRefCount++;
+
+ ObjClockFmtScn(objPtr) = fss;
+ }
+
+ Tcl_MutexUnlock(&ClockFmtMutex);
+
+ if (fss == NULL && interp != NULL) {
+ Tcl_AppendResult(interp, "retrieve clock format failed \"",
+ strFmt ? strFmt : "", "\"", NULL);
+ Tcl_SetErrorCode(interp, "TCL", "EINVAL", (char *)NULL);
+ }
+
+ return fss;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * Tcl_GetClockFrmScnFromObj --
+ *
+ * Returns a clock format/scan representation of (*objPtr), if possible.
+ * If something goes wrong, NULL is returned, and if interp is non-NULL,
+ * an error message is written there.
+ *
+ * Results:
+ * Valid representation of type ClockFmtScnStorage.
+ *
+ * Side effects:
+ * Caches the ClockFmtScnStorage reference as the internal rep of (*objPtr)
+ * and in global hash table, shared across all threads.
+ *
+ *----------------------------------------------------------------------
+ */
+
+ClockFmtScnStorage *
+Tcl_GetClockFrmScnFromObj(
+ Tcl_Interp *interp,
+ Tcl_Obj *objPtr)
+{
+ ClockFmtScnStorage *fss;
+
+ if (objPtr->typePtr != &ClockFmtObjType) {
+ if (ClockFmtObj_SetFromAny(interp, objPtr) != TCL_OK) {
+ return NULL;
+ }
+ }
+
+ fss = ObjClockFmtScn(objPtr);
+
+ if (fss == NULL) {
+ fss = FindOrCreateFmtScnStorage(interp, objPtr);
+ }
+
+ return fss;
+}
+/*
+ *----------------------------------------------------------------------
+ *
+ * ClockLocalizeFormat --
+ *
+ * Wrap the format object in options to the localized format,
+ * corresponding given locale.
+ *
+ * This searches localized format in locale catalog, and if not yet
+ * exists, it executes ::tcl::clock::LocalizeFormat in given interpreter
+ * and caches its result in the locale catalog.
+ *
+ * Results:
+ * Localized format object.
+ *
+ * Side effects:
+ * Caches the localized format inside locale catalog.
+ *
+ *----------------------------------------------------------------------
+ */
+
+Tcl_Obj *
+ClockLocalizeFormat(
+ ClockFmtScnCmdArgs *opts)
+{
+ ClockClientData *dataPtr = (ClockClientData *)opts->clientData;
+ Tcl_Obj *valObj = NULL, *keyObj;
+
+ keyObj = ClockFrmObjGetLocFmtKey(opts->interp, opts->formatObj);
+
+ /* special case - format object is not localizable */
+ if (keyObj == opts->formatObj) {
+ return opts->formatObj;
+ }
+
+ /* prevents loss of key object if the format object (where key stored)
+ * becomes changed (loses its internal representation during evals) */
+ Tcl_IncrRefCount(keyObj);
+
+ if (opts->mcDictObj == NULL) {
+ ClockMCDict(opts);
+ if (opts->mcDictObj == NULL)
+ goto done;
+ }
+
+ /* try to find in cache within locale mc-catalog */
+ if (Tcl_DictObjGet(NULL, opts->mcDictObj,
+ keyObj, &valObj) != TCL_OK) {
+ goto done;
+ }
+
+ /* call LocalizeFormat locale format fmtkey */
+ if (valObj == NULL) {
+ Tcl_Obj *callargs[4];
+ callargs[0] = dataPtr->literals[LIT_LOCALIZE_FORMAT];
+ callargs[1] = opts->localeObj;
+ callargs[2] = opts->formatObj;
+ callargs[3] = opts->mcDictObj;
+ if (Tcl_EvalObjv(opts->interp, 4, callargs, 0) == TCL_OK
+ ) {
+ valObj = Tcl_GetObjResult(opts->interp);
+ }
+
+ /* ensure mcDictObj remains unshared */
+ if (opts->mcDictObj->refCount > 1) {
+ /* smart reference (shared dict as object with no ref-counter) */
+ opts->mcDictObj = TclDictObjSmartRef(opts->interp,
+ opts->mcDictObj);
+ }
+ if (!valObj) {
+ goto done;
+ }
+ /* cache it inside mc-dictionary (this incr. ref count of keyObj/valObj) */
+ if (Tcl_DictObjPut(opts->interp, opts->mcDictObj,
+ keyObj, valObj) != TCL_OK
+ ) {
+ valObj = NULL;
+ goto done;
+ }
+
+ Tcl_ResetResult(opts->interp);
+
+ /* check special case - format object is not localizable */
+ if (valObj == opts->formatObj) {
+ /* mark it as unlocalizable, by setting self as key (without refcount incr) */
+ if (valObj->typePtr == &ClockFmtObjType) {
+ Tcl_UnsetObjRef(ObjLocFmtKey(valObj));
+ ObjLocFmtKey(valObj) = valObj;
+ }
+ }
+ }
+
+done:
+
+ Tcl_UnsetObjRef(keyObj);
+ return (opts->formatObj = valObj);
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * FindTokenBegin --
+ *
+ * Find begin of given scan token in string, corresponding token type.
+ *
+ * Results:
+ * Position of token inside string if found. Otherwise - end of string.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static const char *
+FindTokenBegin(
+ const char *p,
+ const char *end,
+ ClockScanToken *tok)
+{
+ char c;
+ if (p < end) {
+ /* next token a known token type */
+ switch (tok->map->type) {
+ case CTOKT_INT:
+ case CTOKT_WIDE:
+ /* should match at least one digit */
+ while (!isdigit(UCHAR(*p)) && (p = Tcl_UtfNext(p)) < end) {};
+ return p;
+ break;
+ case CTOKT_WORD:
+ c = *(tok->tokWord.start);
+ /* should match at least to the first char of this word */
+ while (*p != c && (p = Tcl_UtfNext(p)) < end) {};
+ return p;
+ break;
+ case CTOKT_SPACE:
+ while (!isspace(UCHAR(*p)) && (p = Tcl_UtfNext(p)) < end) {};
+ return p;
+ break;
+ case CTOKT_CHAR:
+ c = *((char *)tok->map->data);
+ while (*p != c && (p = Tcl_UtfNext(p)) < end) {};
+ return p;
+ break;
+ }
+ }
+ return p;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * DetermineGreedySearchLen --
+ *
+ * Determine min/max lengths as exact as possible (speed, greedy match).
+ *
+ * Results:
+ * None. Lengths are stored in *minLenPtr, *maxLenPtr.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+DetermineGreedySearchLen(
+ DateInfo *info, ClockScanToken *tok,
+ int *minLenPtr, int *maxLenPtr)
+{
+ int minLen = tok->map->minSize;
+ int maxLen;
+ const char *p = yyInput + minLen,
+ *end = info->dateEnd;
+
+ /* if still tokens available, try to correct minimum length */
+ if ((tok+1)->map) {
+ end -= tok->endDistance + yySpaceCount;
+ /* find position of next known token */
+ p = FindTokenBegin(p, end, tok+1);
+ if (p < end) {
+ minLen = p - yyInput;
+ }
+ }
+
+ /* max length to the end regarding distance to end (min-width of following tokens) */
+ maxLen = end - yyInput;
+ /* several amendments */
+ if (maxLen > tok->map->maxSize) {
+ maxLen = tok->map->maxSize;
+ };
+ if (minLen < tok->map->minSize) {
+ minLen = tok->map->minSize;
+ }
+ if (minLen > maxLen) {
+ maxLen = minLen;
+ }
+ if (maxLen > info->dateEnd - yyInput) {
+ maxLen = info->dateEnd - yyInput;
+ }
+
+ /* check digits rigth now */
+ if (tok->map->type == CTOKT_INT || tok->map->type == CTOKT_WIDE) {
+ p = yyInput;
+ end = p + maxLen;
+ if (end > info->dateEnd) { end = info->dateEnd; };
+ while (isdigit(UCHAR(*p)) && p < end) { p++; };
+ maxLen = p - yyInput;
+ }
+
+ /* try to get max length more precise for greedy match,
+ * check the next ahead token available there */
+ if (minLen < maxLen && tok->lookAhTok) {
+ ClockScanToken *laTok = tok + tok->lookAhTok + 1;
+ p = yyInput + maxLen;
+ /* regards all possible spaces here (because they are optional) */
+ end = p + tok->lookAhMax + yySpaceCount + 1;
+ if (end > info->dateEnd) {
+ end = info->dateEnd;
+ }
+ p += tok->lookAhMin;
+ if (laTok->map && p < end) {
+ const char *f;
+ /* try to find laTok between [lookAhMin, lookAhMax] */
+ while (minLen < maxLen) {
+ f = FindTokenBegin(p, end, laTok);
+ /* if found (not below lookAhMax) */
+ if (f < end) {
+ break;
+ }
+ /* try again with fewer length */
+ maxLen--;
+ p--;
+ end--;
+ }
+ } else if (p > end) {
+ maxLen -= (p - end);
+ if (maxLen < minLen) {
+ maxLen = minLen;
+ }
+ }
+ }
+
+ *minLenPtr = minLen;
+ *maxLenPtr = maxLen;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * ObjListSearch --
+ *
+ * Find largest part of the input string from start regarding min and
+ * max lengths in the given list (utf-8, case sensitive).
+ *
+ * Results:
+ * TCL_OK - match found, TCL_RETURN - not matched, TCL_ERROR in error case.
+ *
+ * Side effects:
+ * Input points to end of the found token in string.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static inline int
+ObjListSearch(
+ DateInfo *info, int *val,
+ Tcl_Obj **lstv, Tcl_Size lstc,
+ int minLen, int maxLen)
+{
+ Tcl_Size i, l, lf = -1;
+ const char *s, *f, *sf;
+ /* search in list */
+ for (i = 0; i < lstc; i++) {
+ s = TclGetStringFromObj(lstv[i], &l);
+
+ if ( l >= minLen
+ && (f = TclUtfFindEqualNC(yyInput, yyInput + maxLen, s, s + l, &sf)) > yyInput
+ ) {
+ l = f - yyInput;
+ if (l < minLen) {
+ continue;
+ }
+ /* found, try to find longest value (greedy search) */
+ if (l < maxLen && minLen != maxLen) {
+ lf = i;
+ minLen = l + 1;
+ continue;
+ }
+ /* max possible - end of search */
+ *val = i;
+ yyInput += l;
+ break;
+ }
+ }
+
+ /* if found */
+ if (i < lstc) {
+ return TCL_OK;
+ }
+ if (lf >= 0) {
+ *val = lf;
+ yyInput += minLen - 1;
+ return TCL_OK;
+ }
+ return TCL_RETURN;
+}
+#if 0
+/* currently unused */
+
+static int
+LocaleListSearch(ClockFmtScnCmdArgs *opts,
+ DateInfo *info, int mcKey, int *val,
+ int minLen, int maxLen)
+{
+ Tcl_Obj **lstv;
+ Tcl_Size lstc;
+ Tcl_Obj *valObj;
+
+ /* get msgcat value */
+ valObj = ClockMCGet(opts, mcKey);
+ if (valObj == NULL) {
+ return TCL_ERROR;
+ }
+
+ /* is a list */
+ if (TclListObjGetElements(opts->interp, valObj, &lstc, &lstv) != TCL_OK) {
+ return TCL_ERROR;
+ }
+
+ /* search in list */
+ return ObjListSearch(info, val, lstv, lstc,
+ minLen, maxLen);
+}
+#endif
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * ClockMCGetListIdxTree --
+ *
+ * Retrieves localized string indexed tree in the locale catalog for
+ * given literal index mcKey (and builds it on demand).
+ *
+ * Searches localized index in locale catalog, and if not yet exists,
+ * creates string indexed tree and stores it in the locale catalog.
+ *
+ * Results:
+ * Localized string index tree.
+ *
+ * Side effects:
+ * Caches the localized string index tree inside locale catalog.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static TclStrIdxTree *
+ClockMCGetListIdxTree(
+ ClockFmtScnCmdArgs *opts,
+ int mcKey)
+{
+ TclStrIdxTree * idxTree;
+ Tcl_Obj *objPtr = ClockMCGetIdx(opts, mcKey);
+ if ( objPtr != NULL
+ && (idxTree = TclStrIdxTreeGetFromObj(objPtr)) != NULL
+ ) {
+ return idxTree;
+
+ } else {
+ /* build new index */
+
+ Tcl_Obj **lstv;
+ Tcl_Size lstc;
+ Tcl_Obj *valObj;
+
+ objPtr = TclStrIdxTreeNewObj();
+ if ((idxTree = TclStrIdxTreeGetFromObj(objPtr)) == NULL) {
+ goto done; /* unexpected, but ...*/
+ }
+
+ valObj = ClockMCGet(opts, mcKey);
+ if (valObj == NULL) {
+ goto done;
+ }
+
+ if (TclListObjGetElements(opts->interp, valObj,
+ &lstc, &lstv) != TCL_OK) {
+ goto done;
+ };
+
+ if (TclStrIdxTreeBuildFromList(idxTree, lstc, lstv, NULL) != TCL_OK) {
+ goto done;
+ }
+
+ ClockMCSetIdx(opts, mcKey, objPtr);
+ objPtr = NULL;
+ };
+
+done:
+ if (objPtr) {
+ Tcl_DecrRefCount(objPtr);
+ idxTree = NULL;
+ }
+
+ return idxTree;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * ClockMCGetMultiListIdxTree --
+ *
+ * Retrieves localized string indexed tree in the locale catalog for
+ * multiple lists by literal indices mcKeys (and builds it on demand).
+ *
+ * Searches localized index in locale catalog for mcKey, and if not
+ * yet exists, creates string indexed tree and stores it in the
+ * locale catalog.
+ *
+ * Results:
+ * Localized string index tree.
+ *
+ * Side effects:
+ * Caches the localized string index tree inside locale catalog.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static TclStrIdxTree *
+ClockMCGetMultiListIdxTree(
+ ClockFmtScnCmdArgs *opts,
+ int mcKey,
+ int *mcKeys)
+{
+ TclStrIdxTree * idxTree;
+ Tcl_Obj *objPtr = ClockMCGetIdx(opts, mcKey);
+ if ( objPtr != NULL
+ && (idxTree = TclStrIdxTreeGetFromObj(objPtr)) != NULL
+ ) {
+ return idxTree;
+
+ } else {
+ /* build new index */
+
+ Tcl_Obj **lstv;
+ Tcl_Size lstc;
+ Tcl_Obj *valObj;
+
+ objPtr = TclStrIdxTreeNewObj();
+ if ((idxTree = TclStrIdxTreeGetFromObj(objPtr)) == NULL) {
+ goto done; /* unexpected, but ...*/
+ }
+
+ while (*mcKeys) {
+
+ valObj = ClockMCGet(opts, *mcKeys);
+ if (valObj == NULL) {
+ goto done;
+ }
+
+ if (TclListObjGetElements(opts->interp, valObj,
+ &lstc, &lstv) != TCL_OK) {
+ goto done;
+ };
+
+ if (TclStrIdxTreeBuildFromList(idxTree, lstc, lstv, NULL) != TCL_OK) {
+ goto done;
+ }
+ mcKeys++;
+ }
+
+ ClockMCSetIdx(opts, mcKey, objPtr);
+ objPtr = NULL;
+ };
+
+done:
+ if (objPtr) {
+ Tcl_DecrRefCount(objPtr);
+ idxTree = NULL;
+ }
+
+ return idxTree;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * ClockStrIdxTreeSearch --
+ *
+ * Find largest part of the input string from start regarding lengths
+ * in the given localized string indexed tree (utf-8, case sensitive).
+ *
+ * Results:
+ * TCL_OK - match found and the index stored in *val,
+ * TCL_RETURN - not matched or ambigous,
+ * TCL_ERROR - in error case.
+ *
+ * Side effects:
+ * Input points to end of the found token in string.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static inline int
+ClockStrIdxTreeSearch(
+ DateInfo *info, TclStrIdxTree *idxTree, int *val,
+ int minLen, int maxLen)
+{
+ const char *f;
+ TclStrIdx *foundItem;
+ f = TclStrIdxTreeSearch(NULL, &foundItem, idxTree,
+ yyInput, yyInput + maxLen);
+
+ if (f <= yyInput || (f - yyInput) < minLen) {
+ /* not found */
+ return TCL_RETURN;
+ }
+ if (!foundItem->value) {
+ /* ambigous */
+ return TCL_RETURN;
+ }
+
+ *val = PTR2INT(foundItem->value);
+
+ /* shift input pointer */
+ yyInput = f;
+
+ return TCL_OK;
+}
+#if 0
+/* currently unused */
+
+static int
+StaticListSearch(ClockFmtScnCmdArgs *opts,
+ DateInfo *info, const char **lst, int *val)
+{
+ size_t len;
+ const char **s = lst;
+ while (*s != NULL) {
+ len = strlen(*s);
+ if ( len <= info->dateEnd - yyInput
+ && strncasecmp(yyInput, *s, len) == 0
+ ) {
+ *val = (s - lst);
+ yyInput += len;
+ break;
+ }
+ s++;
+ }
+ if (*s != NULL) {
+ return TCL_OK;
+ }
+ return TCL_RETURN;
+}
+#endif
+
+static inline const char *
+FindWordEnd(
+ ClockScanToken *tok,
+ const char * p, const char * end)
+{
+ const char *x = tok->tokWord.start;
+ const char *pfnd = p;
+ if (x == tok->tokWord.end - 1) { /* fast phase-out for single char word */
+ if (*p == *x) {
+ return ++p;
+ }
+ }
+ /* multi-char word */
+ x = TclUtfFindEqualNC(x, tok->tokWord.end, p, end, &pfnd);
+ if (x < tok->tokWord.end) {
+ /* no match -> error */
+ return NULL;
+ }
+ return pfnd;
+}
+
+static int
+ClockScnToken_Month_Proc(ClockFmtScnCmdArgs *opts,
+ DateInfo *info, ClockScanToken *tok)
+{
+#if 0
+/* currently unused, test purposes only */
+ static const char * months[] = {
+ /* full */
+ "January", "February", "March",
+ "April", "May", "June",
+ "July", "August", "September",
+ "October", "November", "December",
+ /* abbr */
+ "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
+ NULL
+ };
+ int val;
+ if (StaticListSearch(opts, info, months, &val) != TCL_OK) {
+ return TCL_RETURN;
+ }
+ yyMonth = (val % 12) + 1;
+ return TCL_OK;
+#endif
+
+ static int monthsKeys[] = {MCLIT_MONTHS_FULL, MCLIT_MONTHS_ABBREV, 0};
+
+ int ret, val;
+ int minLen, maxLen;
+ TclStrIdxTree *idxTree;
+
+ DetermineGreedySearchLen(info, tok, &minLen, &maxLen);
+
+ /* get or create tree in msgcat dict */
+
+ idxTree = ClockMCGetMultiListIdxTree(opts, MCLIT_MONTHS_COMB, monthsKeys);
+ if (idxTree == NULL) {
+ return TCL_ERROR;
+ }
+
+ ret = ClockStrIdxTreeSearch(info, idxTree, &val, minLen, maxLen);
+ if (ret != TCL_OK) {
+ return ret;
+ }
+
+ yyMonth = val;
+ return TCL_OK;
+
+}
+
+static int
+ClockScnToken_DayOfWeek_Proc(ClockFmtScnCmdArgs *opts,
+ DateInfo *info, ClockScanToken *tok)
+{
+ static int dowKeys[] = {MCLIT_DAYS_OF_WEEK_ABBREV, MCLIT_DAYS_OF_WEEK_FULL, 0};
+
+ int ret, val;
+ int minLen, maxLen;
+ char curTok = *tok->tokWord.start;
+ TclStrIdxTree *idxTree;
+
+ DetermineGreedySearchLen(info, tok, &minLen, &maxLen);
+
+ /* %u %w %Ou %Ow */
+ if ( curTok != 'a' && curTok != 'A'
+ && ((minLen <= 1 && maxLen >= 1) || PTR2INT(tok->map->data))
+ ) {
+
+ val = -1;
+
+ if (PTR2INT(tok->map->data) == 0) {
+ if (*yyInput >= '0' && *yyInput <= '9') {
+ val = *yyInput - '0';
+ }
+ } else {
+ idxTree = ClockMCGetListIdxTree(opts, PTR2INT(tok->map->data) /* mcKey */);
+ if (idxTree == NULL) {
+ return TCL_ERROR;
+ }
+
+ ret = ClockStrIdxTreeSearch(info, idxTree, &val, minLen, maxLen);
+ if (ret != TCL_OK) {
+ return ret;
+ }
+ --val;
+ }
+
+ if (val != -1) {
+ if (val == 0) {
+ val = 7;
+ }
+ if (val > 7) {
+ Tcl_SetObjResult(opts->interp, Tcl_NewStringObj("day of week is greater than 7", -1));
+ Tcl_SetErrorCode(opts->interp, "CLOCK", "badDayOfWeek", (char *)NULL);
+ return TCL_ERROR;
+ }
+ info->date.dayOfWeek = val;
+ yyInput++;
+ return TCL_OK;
+ }
+
+
+ return TCL_RETURN;
+ }
+
+ /* %a %A */
+ idxTree = ClockMCGetMultiListIdxTree(opts, MCLIT_DAYS_OF_WEEK_COMB, dowKeys);
+ if (idxTree == NULL) {
+ return TCL_ERROR;
+ }
+
+ ret = ClockStrIdxTreeSearch(info, idxTree, &val, minLen, maxLen);
+ if (ret != TCL_OK) {
+ return ret;
+ }
+ --val;
+
+ if (val == 0) {
+ val = 7;
+ }
+ info->date.dayOfWeek = val;
+ return TCL_OK;
+
+}
+
+static int
+ClockScnToken_amPmInd_Proc(ClockFmtScnCmdArgs *opts,
+ DateInfo *info, ClockScanToken *tok)
+{
+ int ret, val;
+ int minLen, maxLen;
+ Tcl_Obj *amPmObj[2];
+
+ DetermineGreedySearchLen(info, tok, &minLen, &maxLen);
+
+ amPmObj[0] = ClockMCGet(opts, MCLIT_AM);
+ amPmObj[1] = ClockMCGet(opts, MCLIT_PM);
+
+ if (amPmObj[0] == NULL || amPmObj[1] == NULL) {
+ return TCL_ERROR;
+ }
+
+ ret = ObjListSearch(info, &val, amPmObj, 2,
+ minLen, maxLen);
+ if (ret != TCL_OK) {
+ return ret;
+ }
+
+ if (val == 0) {
+ yyMeridian = MERam;
+ } else {
+ yyMeridian = MERpm;
+ }
+
+ return TCL_OK;
+}
+
+static int
+ClockScnToken_LocaleERA_Proc(ClockFmtScnCmdArgs *opts,
+ DateInfo *info, ClockScanToken *tok)
+{
+ ClockClientData *dataPtr = (ClockClientData *)opts->clientData;
+
+ int ret, val;
+ int minLen, maxLen;
+ Tcl_Obj *eraObj[6];
+
+ DetermineGreedySearchLen(info, tok, &minLen, &maxLen);
+
+ eraObj[0] = ClockMCGet(opts, MCLIT_BCE);
+ eraObj[1] = ClockMCGet(opts, MCLIT_CE);
+ eraObj[2] = dataPtr->mcLiterals[MCLIT_BCE2];
+ eraObj[3] = dataPtr->mcLiterals[MCLIT_CE2];
+ eraObj[4] = dataPtr->mcLiterals[MCLIT_BCE3];
+ eraObj[5] = dataPtr->mcLiterals[MCLIT_CE3];
+
+ if (eraObj[0] == NULL || eraObj[1] == NULL) {
+ return TCL_ERROR;
+ }
+
+ ret = ObjListSearch(info, &val, eraObj, 6,
+ minLen, maxLen);
+ if (ret != TCL_OK) {
+ return ret;
+ }
+
+ if (val & 1) {
+ yydate.isBce = 0;
+ } else {
+ yydate.isBce = 1;
+ }
+
+ return TCL_OK;
+}
+
+static int
+ClockScnToken_LocaleListMatcher_Proc(ClockFmtScnCmdArgs *opts,
+ DateInfo *info, ClockScanToken *tok)
+{
+ int ret, val;
+ int minLen, maxLen;
+ TclStrIdxTree *idxTree;
+
+ DetermineGreedySearchLen(info, tok, &minLen, &maxLen);
+
+ /* get or create tree in msgcat dict */
+
+ idxTree = ClockMCGetListIdxTree(opts, PTR2INT(tok->map->data) /* mcKey */);
+ if (idxTree == NULL) {
+ return TCL_ERROR;
+ }
+
+ ret = ClockStrIdxTreeSearch(info, idxTree, &val, minLen, maxLen);
+ if (ret != TCL_OK) {
+ return ret;
+ }
+
+ if (tok->map->offs > 0) {
+ *(int *)(((char *)info) + tok->map->offs) = --val;
+ }
+
+ return TCL_OK;
+}
+
+static int
+ClockScnToken_JDN_Proc(TCL_UNUSED(ClockFmtScnCmdArgs *),
+ DateInfo *info, ClockScanToken *tok)
+{
+ int minLen, maxLen;
+ const char *p = yyInput, *end; const char *s;
+ Tcl_WideInt intJD; int fractJD = 0, fractJDDiv = 1;
+
+ DetermineGreedySearchLen(info, tok, &minLen, &maxLen);
+
+ end = yyInput + maxLen;
+
+ /* currently positive astronomic dates only */
+ if (*p == '+' || *p == '-') { p++; };
+ s = p;
+ while (p < end && isdigit(UCHAR(*p))) {
+ p++;
+ }
+ if ( _str2wideInt(&intJD, s, p, (*yyInput != '-' ? 1 : -1)) != TCL_OK) {
+ return TCL_RETURN;
+ };
+ yyInput = p;
+ if (p >= end || *p++ != '.') { /* allow pure integer JDN */
+ /* by astronomical JD the seconds of day offs is 12 hours */
+ if (tok->map->offs) {
+ goto done;
+ }
+ /* calendar JD */
+ yydate.julianDay = intJD;
+ return TCL_OK;
+ }
+ s = p;
+ while (p < end && isdigit(UCHAR(*p))) {
+ fractJDDiv *= 10;
+ p++;
+ }
+ if ( _str2int(&fractJD, s, p, 1) != TCL_OK) {
+ return TCL_RETURN;
+ };
+ yyInput = p;
+
+done:
+ /*
+ * Build a date from julian day (integer and fraction).
+ * Note, astronomical JDN starts at noon in opposite to calendar julianday.
+ */
+
+ fractJD = (int)tok->map->offs /* 0 for calendar or 43200 for astro JD */
+ + (int)((Tcl_WideInt)SECONDS_PER_DAY * fractJD / fractJDDiv);
+ if (fractJD > SECONDS_PER_DAY) {
+ fractJD %= SECONDS_PER_DAY;
+ intJD += 1;
+ }
+ yydate.secondOfDay = fractJD;
+ yydate.julianDay = intJD;
+
+ yydate.seconds =
+ -210866803200LL
+ + ( SECONDS_PER_DAY * intJD )
+ + ( fractJD );
+
+ info->flags |= CLF_POSIXSEC;
+
+ return TCL_OK;
+}
+
+static int
+ClockScnToken_TimeZone_Proc(ClockFmtScnCmdArgs *opts,
+ DateInfo *info, ClockScanToken *tok)
+{
+ int minLen, maxLen;
+ int len = 0;
+ const char *p = yyInput;
+ Tcl_Obj *tzObjStor = NULL;
+
+ DetermineGreedySearchLen(info, tok, &minLen, &maxLen);
+
+ /* numeric timezone */
+ if (*p == '+' || *p == '-') {
+ /* max chars in numeric zone = "+00:00:00" */
+ #define MAX_ZONE_LEN 9
+ char buf[MAX_ZONE_LEN + 1];
+ char *bp = buf;
+ *bp++ = *p++; len++;
+ if (maxLen > MAX_ZONE_LEN)
+ maxLen = MAX_ZONE_LEN;
+ /* cumulate zone into buf without ':' */
+ while (len + 1 < maxLen) {
+ if (!isdigit(UCHAR(*p))) break;
+ *bp++ = *p++; len++;
+ if (!isdigit(UCHAR(*p))) break;
+ *bp++ = *p++; len++;
+ if (len + 2 < maxLen) {
+ if (*p == ':') {
+ p++; len++;
+ }
+ }
+ }
+ *bp = '\0';
+
+ if (len < minLen) {
+ return TCL_RETURN;
+ }
+ #undef MAX_ZONE_LEN
+
+ /* timezone */
+ tzObjStor = Tcl_NewStringObj(buf, bp-buf);
+ } else {
+ /* legacy (alnum) timezone like CEST, etc. */
+ if (maxLen > 4)
+ maxLen = 4;
+ while (len < maxLen) {
+ if ( (*p & 0x80)
+ || (!isalpha(UCHAR(*p)) && !isdigit(UCHAR(*p)))
+ ) { /* INTL: ISO only. */
+ break;
+ }
+ p++; len++;
+ }
+
+ if (len < minLen) {
+ return TCL_RETURN;
+ }
+
+ /* timezone */
+ tzObjStor = Tcl_NewStringObj(yyInput, p-yyInput);
+
+ /* convert using dict */
+ }
+
+ /* try to apply new time zone */
+ Tcl_IncrRefCount(tzObjStor);
+
+ opts->timezoneObj = ClockSetupTimeZone(opts->clientData, opts->interp,
+ tzObjStor);
+
+ Tcl_DecrRefCount(tzObjStor);
+ if (opts->timezoneObj == NULL) {
+ return TCL_ERROR;
+ }
+
+ yyInput += len;
+
+ return TCL_OK;
+}
+
+static int
+ClockScnToken_StarDate_Proc(TCL_UNUSED(ClockFmtScnCmdArgs *),
+ DateInfo *info, ClockScanToken *tok)
+{
+ int minLen, maxLen;
+ const char *p = yyInput, *end; const char *s;
+ int year, fractYear, fractDayDiv, fractDay;
+ static const char *stardatePref = "stardate ";
+
+ DetermineGreedySearchLen(info, tok, &minLen, &maxLen);
+
+ end = yyInput + maxLen;
+
+ /* stardate string */
+ p = TclUtfFindEqualNCInLwr(p, end, stardatePref, stardatePref + 9, &s);
+ if (p >= end || p - yyInput < 9) {
+ return TCL_RETURN;
+ }
+ /* bypass spaces */
+ while (p < end && isspace(UCHAR(*p))) {
+ p++;
+ }
+ if (p >= end) {
+ return TCL_RETURN;
+ }
+ /* currently positive stardate only */
+ if (*p == '+') { p++; };
+ s = p;
+ while (p < end && isdigit(UCHAR(*p))) {
+ p++;
+ }
+ if (p >= end || p - s < 4) {
+ return TCL_RETURN;
+ }
+ if ( _str2int(&year, s, p-3, 1) != TCL_OK
+ || _str2int(&fractYear, p-3, p, 1) != TCL_OK) {
+ return TCL_RETURN;
+ };
+ if (*p++ != '.') {
+ return TCL_RETURN;
+ }
+ s = p;
+ fractDayDiv = 1;
+ while (p < end && isdigit(UCHAR(*p))) {
+ fractDayDiv *= 10;
+ p++;
+ }
+ if ( _str2int(&fractDay, s, p, 1) != TCL_OK) {
+ return TCL_RETURN;
+ };
+ yyInput = p;
+
+ /* Build a date from year and fraction. */
+
+ yydate.year = year + RODDENBERRY;
+ yydate.isBce = 0;
+ yydate.gregorian = 1;
+
+ if (IsGregorianLeapYear(&yydate)) {
+ fractYear *= 366;
+ } else {
+ fractYear *= 365;
+ }
+ yydate.dayOfYear = fractYear / 1000 + 1;
+ if (fractYear % 1000 >= 500) {
+ yydate.dayOfYear++;
+ }
+
+ GetJulianDayFromEraYearDay(&yydate, GREGORIAN_CHANGE_DATE);
+
+ yydate.localSeconds =
+ -210866803200LL
+ + ( SECONDS_PER_DAY * yydate.julianDay )
+ + ( SECONDS_PER_DAY * fractDay / fractDayDiv );
+
+ return TCL_OK;
+}
+
+static const char *ScnSTokenMapIndex =
+ "dmbyYHMSpJjCgGVazUsntQ";
+static ClockScanTokenMap ScnSTokenMap[] = {
+ /* %d %e */
+ {CTOKT_INT, CLF_DAYOFMONTH, 0, 1, 2, offsetof(DateInfo, date.dayOfMonth),
+ NULL, NULL},
+ /* %m %N */
+ {CTOKT_INT, CLF_MONTH, 0, 1, 2, offsetof(DateInfo, date.month),
+ NULL, NULL},
+ /* %b %B %h */
+ {CTOKT_PARSER, CLF_MONTH, 0, 0, 0xffff, 0,
+ ClockScnToken_Month_Proc, NULL},
+ /* %y */
+ {CTOKT_INT, CLF_YEAR, 0, 1, 2, offsetof(DateInfo, date.year),
+ NULL, NULL},
+ /* %Y */
+ {CTOKT_INT, CLF_YEAR | CLF_CENTURY, 0, 4, 4, offsetof(DateInfo, date.year),
+ NULL, NULL},
+ /* %H %k %I %l */
+ {CTOKT_INT, CLF_TIME, 0, 1, 2, offsetof(DateInfo, date.hour),
+ NULL, NULL},
+ /* %M */
+ {CTOKT_INT, CLF_TIME, 0, 1, 2, offsetof(DateInfo, date.minutes),
+ NULL, NULL},
+ /* %S */
+ {CTOKT_INT, CLF_TIME, 0, 1, 2, offsetof(DateInfo, date.secondOfMin),
+ NULL, NULL},
+ /* %p %P */
+ {CTOKT_PARSER, 0, 0, 0, 0xffff, 0,
+ ClockScnToken_amPmInd_Proc, NULL},
+ /* %J */
+ {CTOKT_WIDE, CLF_JULIANDAY | CLF_SIGNED, 0, 1, 0xffff, offsetof(DateInfo, date.julianDay),
+ NULL, NULL},
+ /* %j */
+ {CTOKT_INT, CLF_DAYOFYEAR, 0, 1, 3, offsetof(DateInfo, date.dayOfYear),
+ NULL, NULL},
+ /* %C */
+ {CTOKT_INT, CLF_CENTURY|CLF_ISO8601CENTURY, 0, 1, 2, offsetof(DateInfo, dateCentury),
+ NULL, NULL},
+ /* %g */
+ {CTOKT_INT, CLF_ISO8601YEAR, 0, 2, 2, offsetof(DateInfo, date.iso8601Year),
+ NULL, NULL},
+ /* %G */
+ {CTOKT_INT, CLF_ISO8601YEAR | CLF_ISO8601CENTURY, 0, 4, 4, offsetof(DateInfo, date.iso8601Year),
+ NULL, NULL},
+ /* %V */
+ {CTOKT_INT, CLF_ISO8601WEAK, 0, 1, 2, offsetof(DateInfo, date.iso8601Week),
+ NULL, NULL},
+ /* %a %A %u %w */
+ {CTOKT_PARSER, CLF_DAYOFWEEK, 0, 0, 0xffff, 0,
+ ClockScnToken_DayOfWeek_Proc, NULL},
+ /* %z %Z */
+ {CTOKT_PARSER, CLF_OPTIONAL, 0, 0, 0xffff, 0,
+ ClockScnToken_TimeZone_Proc, NULL},
+ /* %U %W */
+ {CTOKT_INT, CLF_OPTIONAL, 0, 1, 2, 0, /* currently no capture, parse only token */
+ NULL, NULL},
+ /* %s */
+ {CTOKT_WIDE, CLF_POSIXSEC | CLF_SIGNED, 0, 1, 0xffff, offsetof(DateInfo, date.seconds),
+ NULL, NULL},
+ /* %n */
+ {CTOKT_CHAR, 0, 0, 1, 1, 0, NULL, "\n"},
+ /* %t */
+ {CTOKT_CHAR, 0, 0, 1, 1, 0, NULL, "\t"},
+ /* %Q */
+ {CTOKT_PARSER, CLF_LOCALSEC, 0, 16, 30, 0,
+ ClockScnToken_StarDate_Proc, NULL},
+};
+static const char *ScnSTokenMapAliasIndex[2] = {
+ "eNBhkIlPAuwZW",
+ "dmbbHHHpaaazU"
+};
+
+static const char *ScnETokenMapIndex =
+ "EJjys";
+static ClockScanTokenMap ScnETokenMap[] = {
+ /* %EE */
+ {CTOKT_PARSER, 0, 0, 0, 0xffff, offsetof(DateInfo, date.year),
+ ClockScnToken_LocaleERA_Proc, (void *)MCLIT_LOCALE_NUMERALS},
+ /* %EJ */
+ {CTOKT_PARSER, CLF_JULIANDAY | CLF_SIGNED, 0, 1, 0xffff, 0, /* calendar JDN starts at midnight */
+ ClockScnToken_JDN_Proc, NULL},
+ /* %Ej */
+ {CTOKT_PARSER, CLF_JULIANDAY | CLF_SIGNED, 0, 1, 0xffff, (SECONDS_PER_DAY/2), /* astro JDN starts at noon */
+ ClockScnToken_JDN_Proc, NULL},
+ /* %Ey */
+ {CTOKT_PARSER, 0, 0, 0, 0xffff, 0, /* currently no capture, parse only token */
+ ClockScnToken_LocaleListMatcher_Proc, (void *)MCLIT_LOCALE_NUMERALS},
+ /* %Es */
+ {CTOKT_WIDE, CLF_LOCALSEC | CLF_SIGNED, 0, 1, 0xffff, offsetof(DateInfo, date.localSeconds),
+ NULL, NULL},
+};
+static const char *ScnETokenMapAliasIndex[2] = {
+ "",
+ ""
+};
+
+static const char *ScnOTokenMapIndex =
+ "dmyHMSu";
+static ClockScanTokenMap ScnOTokenMap[] = {
+ /* %Od %Oe */
+ {CTOKT_PARSER, CLF_DAYOFMONTH, 0, 0, 0xffff, offsetof(DateInfo, date.dayOfMonth),
+ ClockScnToken_LocaleListMatcher_Proc, (void *)MCLIT_LOCALE_NUMERALS},
+ /* %Om */
+ {CTOKT_PARSER, CLF_MONTH, 0, 0, 0xffff, offsetof(DateInfo, date.month),
+ ClockScnToken_LocaleListMatcher_Proc, (void *)MCLIT_LOCALE_NUMERALS},
+ /* %Oy */
+ {CTOKT_PARSER, CLF_YEAR, 0, 0, 0xffff, offsetof(DateInfo, date.year),
+ ClockScnToken_LocaleListMatcher_Proc, (void *)MCLIT_LOCALE_NUMERALS},
+ /* %OH %Ok %OI %Ol */
+ {CTOKT_PARSER, CLF_TIME, 0, 0, 0xffff, offsetof(DateInfo, date.hour),
+ ClockScnToken_LocaleListMatcher_Proc, (void *)MCLIT_LOCALE_NUMERALS},
+ /* %OM */
+ {CTOKT_PARSER, CLF_TIME, 0, 0, 0xffff, offsetof(DateInfo, date.minutes),
+ ClockScnToken_LocaleListMatcher_Proc, (void *)MCLIT_LOCALE_NUMERALS},
+ /* %OS */
+ {CTOKT_PARSER, CLF_TIME, 0, 0, 0xffff, offsetof(DateInfo, date.secondOfMin),
+ ClockScnToken_LocaleListMatcher_Proc, (void *)MCLIT_LOCALE_NUMERALS},
+ /* %Ou Ow */
+ {CTOKT_PARSER, CLF_DAYOFWEEK, 0, 0, 0xffff, 0,
+ ClockScnToken_DayOfWeek_Proc, (void *)MCLIT_LOCALE_NUMERALS},
+};
+static const char *ScnOTokenMapAliasIndex[2] = {
+ "ekIlw",
+ "dHHHu"
+};
+
+/* Token map reserved for CTOKT_SPACE */
+static ClockScanTokenMap ScnSpaceTokenMap = {
+ CTOKT_SPACE, 0, 0, 1, 1, 0,
+ NULL, NULL
+};
+
+static ClockScanTokenMap ScnWordTokenMap = {
+ CTOKT_WORD, 0, 0, 1, 1, 0,
+ NULL, NULL
+};
+
+
+static inline unsigned int
+EstimateTokenCount(
+ const char *fmt,
+ const char *end)
+{
+ const char *p = fmt;
+ unsigned int tokcnt;
+ /* estimate token count by % char and format length */
+ tokcnt = 0;
+ while (p <= end) {
+ if (*p++ == '%') {
+ tokcnt++;
+ p++;
+ }
+ }
+ p = fmt + tokcnt * 2;
+ if (p < end) {
+ if ((unsigned int)(end - p) < tokcnt) {
+ tokcnt += (end - p);
+ } else {
+ tokcnt += tokcnt;
+ }
+ }
+ return ++tokcnt;
+}
+
+#define AllocTokenInChain(tok, chain, tokCnt, type) \
+ if (++(tok) >= (chain) + (tokCnt)) { \
+ chain = (type)Tcl_Realloc((char *)(chain), \
+ (tokCnt + CLOCK_MIN_TOK_CHAIN_BLOCK_SIZE) * sizeof(*(tok))); \
+ if ((chain) == NULL) { goto done; }; \
+ (tok) = (chain) + (tokCnt); \
+ (tokCnt) += CLOCK_MIN_TOK_CHAIN_BLOCK_SIZE; \
+ } \
+ memset(tok, 0, sizeof(*(tok)));
+
+/*
+ *----------------------------------------------------------------------
+ */
+ClockFmtScnStorage *
+ClockGetOrParseScanFormat(
+ Tcl_Interp *interp, /* Tcl interpreter */
+ Tcl_Obj *formatObj) /* Format container */
+{
+ ClockFmtScnStorage *fss;
+
+ fss = Tcl_GetClockFrmScnFromObj(interp, formatObj);
+ if (fss == NULL) {
+ return NULL;
+ }
+
+ /* if format (scnTok) already tokenized */
+ if (fss->scnTok != NULL) {
+ return fss;
+ }
+
+ Tcl_MutexLock(&ClockFmtMutex);
+
+ /* first time scanning - tokenize format */
+ if (fss->scnTok == NULL) {
+ ClockScanToken *tok, *scnTok;
+ unsigned int tokCnt;
+ const char *p, *e, *cp;
+
+ e = p = HashEntry4FmtScn(fss)->key.string;
+ e += strlen(p);
+
+ /* estimate token count by % char and format length */
+ fss->scnTokC = EstimateTokenCount(p, e);
+
+ fss->scnSpaceCount = 0;
+
+ scnTok = tok = (ClockScanToken *)Tcl_Alloc(sizeof(*tok) * fss->scnTokC);
+ memset(tok, 0, sizeof(*(tok)));
+ tokCnt = 1;
+ while (p < e) {
+ switch (*p) {
+ case '%':
+ if (1) {
+ ClockScanTokenMap * scnMap = ScnSTokenMap;
+ const char *mapIndex = ScnSTokenMapIndex,
+ **aliasIndex = ScnSTokenMapAliasIndex;
+ if (p+1 >= e) {
+ goto word_tok;
+ }
+ p++;
+ /* try to find modifier: */
+ switch (*p) {
+ case '%':
+ /* begin new word token - don't join with previous word token,
+ * because current mapping should be "...%%..." -> "...%..." */
+ tok->map = &ScnWordTokenMap;
+ tok->tokWord.start = p;
+ tok->tokWord.end = p+1;
+ AllocTokenInChain(tok, scnTok, fss->scnTokC, ClockScanToken *); tokCnt++;
+ p++;
+ continue;
+ break;
+ case 'E':
+ scnMap = ScnETokenMap,
+ mapIndex = ScnETokenMapIndex,
+ aliasIndex = ScnETokenMapAliasIndex;
+ p++;
+ break;
+ case 'O':
+ scnMap = ScnOTokenMap,
+ mapIndex = ScnOTokenMapIndex,
+ aliasIndex = ScnOTokenMapAliasIndex;
+ p++;
+ break;
+ }
+ /* search direct index */
+ cp = strchr(mapIndex, *p);
+ if (!cp || *cp == '\0') {
+ /* search wrapper index (multiple chars for same token) */
+ cp = strchr(aliasIndex[0], *p);
+ if (!cp || *cp == '\0') {
+ p--; if (scnMap != ScnSTokenMap) p--;
+ goto word_tok;
+ }
+ cp = strchr(mapIndex, aliasIndex[1][cp - aliasIndex[0]]);
+ if (!cp || *cp == '\0') { /* unexpected, but ... */
+ #ifdef DEBUG
+ Tcl_Panic("token \"%c\" has no map in wrapper resolver", *p);
+ #endif
+ p--; if (scnMap != ScnSTokenMap) p--;
+ goto word_tok;
+ }
+ }
+ tok->map = &scnMap[cp - mapIndex];
+ tok->tokWord.start = p;
+
+ /* calculate look ahead value by standing together tokens */
+ if (tok > scnTok) {
+ ClockScanToken *prevTok = tok - 1;
+
+ while (prevTok >= scnTok) {
+ if (prevTok->map->type != tok->map->type) {
+ break;
+ }
+ prevTok->lookAhMin += tok->map->minSize;
+ prevTok->lookAhMax += tok->map->maxSize;
+ prevTok->lookAhTok++;
+ prevTok--;
+ }
+ }
+
+ /* increase space count used in format */
+ if ( tok->map->type == CTOKT_CHAR
+ && isspace(UCHAR(*((char *)tok->map->data)))
+ ) {
+ fss->scnSpaceCount++;
+ }
+
+ /* next token */
+ AllocTokenInChain(tok, scnTok, fss->scnTokC, ClockScanToken *); tokCnt++;
+ p++;
+ continue;
+ }
+ break;
+ default:
+ if ( *p == ' ' || isspace(UCHAR(*p)) ) {
+ tok->map = &ScnSpaceTokenMap;
+ tok->tokWord.start = p++;
+ while (p < e && isspace(UCHAR(*p))) {
+ p++;
+ }
+ tok->tokWord.end = p;
+ /* increase space count used in format */
+ fss->scnSpaceCount++;
+ /* next token */
+ AllocTokenInChain(tok, scnTok, fss->scnTokC, ClockScanToken *); tokCnt++;
+ continue;
+ }
+word_tok:
+ if (1) {
+ ClockScanToken *wordTok = tok;
+ if (tok > scnTok && (tok-1)->map == &ScnWordTokenMap) {
+ wordTok = tok-1;
+ }
+ /* new word token */
+ if (wordTok == tok) {
+ wordTok->tokWord.start = p;
+ wordTok->map = &ScnWordTokenMap;
+ AllocTokenInChain(tok, scnTok, fss->scnTokC, ClockScanToken *); tokCnt++;
+ }
+ if (isspace(UCHAR(*p))) {
+ fss->scnSpaceCount++;
+ }
+ p = Tcl_UtfNext(p);
+ wordTok->tokWord.end = p;
+ }
+ break;
+ }
+ }
+
+ /* calculate end distance value for each tokens */
+ if (tok > scnTok) {
+ unsigned int endDist = 0;
+ ClockScanToken *prevTok = tok-1;
+
+ while (prevTok >= scnTok) {
+ prevTok->endDistance = endDist;
+ if (prevTok->map->type != CTOKT_WORD) {
+ endDist += prevTok->map->minSize;
+ } else {
+ endDist += prevTok->tokWord.end - prevTok->tokWord.start;
+ }
+ prevTok--;
+ }
+ }
+
+ /* correct count of real used tokens and free mem if desired
+ * (1 is acceptable delta to prevent memory fragmentation) */
+ if (fss->scnTokC > tokCnt + (CLOCK_MIN_TOK_CHAIN_BLOCK_SIZE / 2)) {
+ if ( (tok = (ClockScanToken *)Tcl_Realloc(scnTok, tokCnt * sizeof(*tok))) != NULL ) {
+ scnTok = tok;
+ }
+ }
+
+ /* now we're ready - assign now to storage (note the threaded race condition) */
+ fss->scnTok = scnTok;
+ fss->scnTokC = tokCnt;
+ }
+done:
+ Tcl_MutexUnlock(&ClockFmtMutex);
+
+ return fss;
+}
+
+/*
+ *----------------------------------------------------------------------
+ */
+int
+ClockScan(
+ DateInfo *info, /* Date fields used for parsing & converting */
+ Tcl_Obj *strObj, /* String containing the time to scan */
+ ClockFmtScnCmdArgs *opts) /* Command options */
+{
+ ClockClientData *dataPtr = (ClockClientData *)opts->clientData;
+ ClockFmtScnStorage *fss;
+ ClockScanToken *tok;
+ ClockScanTokenMap *map;
+ const char *p, *x, *end;
+ unsigned short int flags = 0;
+ int ret = TCL_ERROR;
+
+ /* get localized format */
+ if (ClockLocalizeFormat(opts) == NULL) {
+ return TCL_ERROR;
+ }
+
+ if ( !(fss = ClockGetOrParseScanFormat(opts->interp, opts->formatObj))
+ || !(tok = fss->scnTok)
+ ) {
+ return TCL_ERROR;
+ }
+
+ /* prepare parsing */
+
+ yyMeridian = MER24;
+
+ p = TclGetString(strObj);
+ end = p + strObj->length;
+ /* in strict mode - bypass spaces at begin / end only (not between tokens) */
+ if (opts->flags & CLF_STRICT) {
+ while (p < end && isspace(UCHAR(*p))) {
+ p++;
+ }
+ }
+ yyInput = p;
+ /* look ahead to count spaces (bypass it by count length and distances) */
+ x = end;
+ while (p < end) {
+ if (isspace(UCHAR(*p))) {
+ x = ++p; /* after first space in space block */
+ yySpaceCount++;
+ while (p < end && isspace(UCHAR(*p))) {
+ p++;
+ yySpaceCount++;
+ }
+ continue;
+ }
+ x = end;
+ p++;
+ }
+ /* ignore more as 1 space at end */
+ yySpaceCount -= (end - x);
+ end = x;
+ /* ignore mandatory spaces used in format */
+ yySpaceCount -= fss->scnSpaceCount;
+ if (yySpaceCount < 0) {
+ yySpaceCount = 0;
+ }
+ info->dateStart = p = yyInput;
+ info->dateEnd = end;
+
+ /* parse string */
+ for (; tok->map != NULL; tok++) {
+ map = tok->map;
+ /* bypass spaces at begin of input before parsing each token */
+ if ( !(opts->flags & CLF_STRICT)
+ && ( map->type != CTOKT_SPACE
+ && map->type != CTOKT_WORD
+ && map->type != CTOKT_CHAR )
+ ) {
+ while (p < end && isspace(UCHAR(*p))) {
+ yySpaceCount--;
+ p++;
+ }
+ }
+ yyInput = p;
+ /* end of input string */
+ if (p >= end) {
+ break;
+ }
+ switch (map->type)
+ {
+ case CTOKT_INT:
+ case CTOKT_WIDE:
+ if (1) {
+ int minLen, size;
+ int sign = 1;
+ if (map->flags & CLF_SIGNED) {
+ if (*p == '+') { yyInput = ++p; }
+ else
+ if (*p == '-') { yyInput = ++p; sign = -1; };
+ }
+
+ DetermineGreedySearchLen(info, tok, &minLen, &size);
+
+ if (size < map->minSize) {
+ /* missing input -> error */
+ if ((map->flags & CLF_OPTIONAL)) {
+ continue;
+ }
+ goto not_match;
+ }
+ /* string 2 number, put number into info structure by offset */
+ if (map->offs) {
+ p = yyInput; x = p + size;
+ if (map->type == CTOKT_INT) {
+ if (_str2int((int *)(((char *)info) + map->offs),
+ p, x, sign) != TCL_OK) {
+ goto overflow;
+ }
+ p = x;
+ } else {
+ if (_str2wideInt((Tcl_WideInt *)(((char *)info) + map->offs),
+ p, x, sign) != TCL_OK) {
+ goto overflow;
+ }
+ p = x;
+ }
+ flags = (flags & ~map->clearFlags) | map->flags;
+ }
+ }
+ break;
+ case CTOKT_PARSER:
+ switch (map->parser(opts, info, tok)) {
+ case TCL_OK:
+ break;
+ case TCL_RETURN:
+ if ((map->flags & CLF_OPTIONAL)) {
+ yyInput = p;
+ continue;
+ }
+ goto not_match;
+ break;
+ default:
+ goto done;
+ break;
+ };
+ /* decrement count for possible spaces in match */
+ while (p < yyInput) {
+ if (isspace(UCHAR(*p))) {
+ yySpaceCount--;
+ }
+ p++;
+ }
+ p = yyInput;
+ flags = (flags & ~map->clearFlags) | map->flags;
+ break;
+ case CTOKT_SPACE:
+ /* at least one space */
+ if (!isspace(UCHAR(*p))) {
+ /* unmatched -> error */
+ goto not_match;
+ }
+ /* don't decrement yySpaceCount by regular (first expected space),
+ * already considered above with fss->scnSpaceCount */;
+ p++;
+ while (p < end && isspace(UCHAR(*p))) {
+ yySpaceCount--;
+ p++;
+ }
+ break;
+ case CTOKT_WORD:
+ x = FindWordEnd(tok, p, end);
+ if (!x) {
+ /* no match -> error */
+ goto not_match;
+ }
+ p = x;
+ break;
+ case CTOKT_CHAR:
+ x = (char *)map->data;
+ if (*x != *p) {
+ /* no match -> error */
+ goto not_match;
+ }
+ if (isspace(UCHAR(*x))) {
+ yySpaceCount--;
+ }
+ p++;
+ break;
+ }
+ }
+ /* check end was reached */
+ if (p < end) {
+ /* in non-strict mode bypass spaces at end of input */
+ if ( !(opts->flags & CLF_STRICT) && isspace(UCHAR(*p)) ) {
+ p++;
+ while (p < end && isspace(UCHAR(*p))) {
+ p++;
+ }
+ }
+ /* something after last token - wrong format */
+ if (p < end) {
+ goto not_match;
+ }
+ }
+ /* end of string, check only optional tokens at end, otherwise - not match */
+ while (tok->map != NULL) {
+ if (!(opts->flags & CLF_STRICT) && (tok->map->type == CTOKT_SPACE)) {
+ tok++;
+ if (tok->map == NULL) {
+ /* no tokens anymore - trailing spaces are mandatory */
+ goto not_match;
+ }
+ }
+ if (!(tok->map->flags & CLF_OPTIONAL)) {
+ goto not_match;
+ }
+ tok++;
+ }
+
+ /*
+ * Invalidate result
+ */
+ flags |= info->flags;
+
+ /* seconds token (%s) take precedence over all other tokens */
+ if ((opts->flags & CLF_EXTENDED) || !(flags & CLF_POSIXSEC)) {
+ if (flags & CLF_DATE) {
+
+ if (!(flags & CLF_JULIANDAY)) {
+ info->flags |= CLF_ASSEMBLE_SECONDS|CLF_ASSEMBLE_JULIANDAY;
+
+ /* dd precedence below ddd */
+ switch (flags & (CLF_MONTH|CLF_DAYOFYEAR|CLF_DAYOFMONTH)) {
+ case (CLF_DAYOFYEAR|CLF_DAYOFMONTH):
+ /* miss month: ddd over dd (without month) */
+ flags &= ~CLF_DAYOFMONTH;
+ /* fallthrough */
+ case (CLF_DAYOFYEAR):
+ /* ddd over naked weekday */
+ if (!(flags & CLF_ISO8601YEAR)) {
+ flags &= ~CLF_ISO8601WEAK;
+ }
+ break;
+ case (CLF_MONTH|CLF_DAYOFYEAR|CLF_DAYOFMONTH):
+ /* both available: mmdd over ddd */
+ case (CLF_MONTH|CLF_DAYOFMONTH):
+ case (CLF_DAYOFMONTH):
+ /* mmdd / dd over naked weekday */
+ if (!(flags & CLF_ISO8601YEAR)) {
+ flags &= ~CLF_ISO8601WEAK;
+ }
+ break;
+ /* neither mmdd nor ddd available */
+ case 0:
+ /* but we have day of the week, which can be used */
+ if (flags & CLF_DAYOFWEEK) {
+ /* prefer week based calculation of julianday */
+ flags |= CLF_ISO8601WEAK;
+ }
+ }
+
+ /* YearWeekDay below YearMonthDay */
+ if ( (flags & CLF_ISO8601WEAK)
+ && ( (flags & (CLF_YEAR|CLF_DAYOFYEAR)) == (CLF_YEAR|CLF_DAYOFYEAR)
+ || (flags & (CLF_YEAR|CLF_DAYOFMONTH|CLF_MONTH)) == (CLF_YEAR|CLF_DAYOFMONTH|CLF_MONTH)
+ )
+ ) {
+ /* yy precedence below yyyy */
+ if (!(flags & CLF_ISO8601CENTURY) && (flags & CLF_CENTURY)) {
+ /* normally precedence of ISO is higher, but no century - so put it down */
+ flags &= ~CLF_ISO8601WEAK;
+ }
+ else
+ /* yymmdd or yyddd over naked weekday */
+ if (!(flags & CLF_ISO8601YEAR)) {
+ flags &= ~CLF_ISO8601WEAK;
+ }
+ }
+
+ if ( (flags & CLF_YEAR) ) {
+ if (yyYear < 100) {
+ if (!(flags & CLF_CENTURY)) {
+ if (yyYear >= dataPtr->yearOfCenturySwitch) {
+ yyYear -= 100;
+ }
+ yyYear += dataPtr->currentYearCentury;
+ } else {
+ yyYear += info->dateCentury * 100;
+ }
+ }
+ }
+ if ( (flags & (CLF_ISO8601WEAK|CLF_ISO8601YEAR)) ) {
+ if ((flags & (CLF_ISO8601YEAR|CLF_YEAR)) == CLF_YEAR) {
+ /* for calculations expected iso year */
+ info->date.iso8601Year = yyYear;
+ }
+ else
+ if (info->date.iso8601Year < 100) {
+ if (!(flags & CLF_ISO8601CENTURY)) {
+ if (info->date.iso8601Year >= dataPtr->yearOfCenturySwitch) {
+ info->date.iso8601Year -= 100;
+ }
+ info->date.iso8601Year += dataPtr->currentYearCentury;
+ } else {
+ info->date.iso8601Year += info->dateCentury * 100;
+ }
+ }
+ if ((flags & (CLF_ISO8601YEAR|CLF_YEAR)) == CLF_ISO8601YEAR) {
+ /* for calculations expected year (e. g. CLF_ISO8601WEAK not set) */
+ yyYear = info->date.iso8601Year;
+ }
+ }
+ }
+ }
+
+ /* if no time - reset time */
+ if (!(flags & (CLF_TIME|CLF_LOCALSEC|CLF_POSIXSEC))) {
+ info->flags |= CLF_ASSEMBLE_SECONDS;
+ yydate.localSeconds = 0;
+ }
+
+ if (flags & CLF_TIME) {
+ info->flags |= CLF_ASSEMBLE_SECONDS;
+ yySecondOfDay = ToSeconds(yyHour, yyMinutes,
+ yySeconds, yyMeridian);
+ } else
+ if (!(flags & (CLF_LOCALSEC|CLF_POSIXSEC))) {
+ info->flags |= CLF_ASSEMBLE_SECONDS;
+ yySecondOfDay = yydate.localSeconds % SECONDS_PER_DAY;
+ }
+ }
+
+ /* tell caller which flags were set */
+ info->flags |= flags;
+
+ ret = TCL_OK;
+ goto done;
+
+overflow:
+
+ Tcl_SetObjResult(opts->interp, Tcl_NewStringObj("integer value too large to represent",
+ -1));
+ Tcl_SetErrorCode(opts->interp, "CLOCK", "dateTooLarge", (char *)NULL);
+ goto done;
+
+not_match:
+
+ #if 1
+ Tcl_SetObjResult(opts->interp, Tcl_NewStringObj("input string does not match supplied format",
+ -1));
+ #else
+ /* to debug where exactly scan breaks */
+ Tcl_SetObjResult(opts->interp, Tcl_ObjPrintf(
+ "input string \"%s\" does not match supplied format \"%s\","
+ " locale \"%s\" - token \"%s\"",
+ info->dateStart, HashEntry4FmtScn(fss)->key.string,
+ Tcl_GetString(opts->localeObj),
+ tok && tok->tokWord.start ? tok->tokWord.start : "NULL"));
+ #endif
+ Tcl_SetErrorCode(opts->interp, "CLOCK", "badInputString", (char *)NULL);
+
+done:
+
+ return ret;
+}
+
+#define FrmResultIsAllocated(dateFmt) \
+ (dateFmt->resEnd - dateFmt->resMem > MIN_FMT_RESULT_BLOCK_ALLOC)
+
+static inline int
+FrmResultAllocate(
+ DateFormat *dateFmt,
+ int len)
+{
+ int needed = dateFmt->output + len - dateFmt->resEnd;
+ if (needed >= 0) { /* >= 0 - regards NTS zero */
+ int newsize = dateFmt->resEnd - dateFmt->resMem
+ + needed + MIN_FMT_RESULT_BLOCK_ALLOC*2;
+ char *newRes;
+ /* differentiate between stack and memory */
+ if (!FrmResultIsAllocated(dateFmt)) {
+ newRes = (char *)Tcl_Alloc(newsize);
+ if (newRes == NULL) {
+ return TCL_ERROR;
+ }
+ memcpy(newRes, dateFmt->resMem, dateFmt->output - dateFmt->resMem);
+ } else {
+ newRes = (char *)Tcl_Realloc(dateFmt->resMem, newsize);
+ if (newRes == NULL) {
+ return TCL_ERROR;
+ }
+ }
+ dateFmt->output = newRes + (dateFmt->output - dateFmt->resMem);
+ dateFmt->resMem = newRes;
+ dateFmt->resEnd = newRes + newsize;
+ }
+ return TCL_OK;
+}
+
+static int
+ClockFmtToken_HourAMPM_Proc(
+ TCL_UNUSED(ClockFmtScnCmdArgs *),
+ TCL_UNUSED(DateFormat *),
+ TCL_UNUSED(ClockFormatToken *),
+ int *val)
+{
+ *val = ( ( *val + SECONDS_PER_DAY - 3600 ) / 3600 ) % 12 + 1;
+ return TCL_OK;
+}
+
+static int
+ClockFmtToken_AMPM_Proc(
+ ClockFmtScnCmdArgs *opts,
+ DateFormat *dateFmt,
+ ClockFormatToken *tok,
+ int *val)
+{
+ Tcl_Obj *mcObj;
+ const char *s;
+ Tcl_Size len;
+
+ if (*val < (SECONDS_PER_DAY / 2)) {
+ mcObj = ClockMCGet(opts, MCLIT_AM);
+ } else {
+ mcObj = ClockMCGet(opts, MCLIT_PM);
+ }
+ if (mcObj == NULL) {
+ return TCL_ERROR;
+ }
+ s = TclGetStringFromObj(mcObj, &len);
+ if (FrmResultAllocate(dateFmt, len) != TCL_OK) { return TCL_ERROR; };
+ memcpy(dateFmt->output, s, len + 1);
+ if (*tok->tokWord.start == 'p') {
+ len = Tcl_UtfToUpper(dateFmt->output);
+ }
+ dateFmt->output += len;
+
+ return TCL_OK;
+}
+
+static int
+ClockFmtToken_StarDate_Proc(
+ TCL_UNUSED(ClockFmtScnCmdArgs *),
+ DateFormat *dateFmt,
+ TCL_UNUSED(ClockFormatToken *),
+ TCL_UNUSED(int *))
+{
+ int fractYear;
+ /* Get day of year, zero based */
+ int v = dateFmt->date.dayOfYear - 1;
+
+ /* Convert day of year to a fractional year */
+ if (IsGregorianLeapYear(&dateFmt->date)) {
+ fractYear = 1000 * v / 366;
+ } else {
+ fractYear = 1000 * v / 365;
+ }
+
+ /* Put together the StarDate as "Stardate %02d%03d.%1d" */
+ if (FrmResultAllocate(dateFmt, 30) != TCL_OK) { return TCL_ERROR; };
+ memcpy(dateFmt->output, "Stardate ", 9);
+ dateFmt->output += 9;
+ dateFmt->output = _itoaw(dateFmt->output,
+ dateFmt->date.year - RODDENBERRY, '0', 2);
+ dateFmt->output = _itoaw(dateFmt->output,
+ fractYear, '0', 3);
+ *dateFmt->output++ = '.';
+ /* be sure positive after decimal point (note: clock-value can be negative) */
+ v = dateFmt->date.secondOfDay / ( SECONDS_PER_DAY / 10 );
+ if (v < 0) v = 10 + v;
+ dateFmt->output = _itoaw(dateFmt->output, v, '0', 1);
+
+ return TCL_OK;
+}
+static int
+ClockFmtToken_WeekOfYear_Proc(
+ TCL_UNUSED(ClockFmtScnCmdArgs *),
+ DateFormat *dateFmt,
+ ClockFormatToken *tok,
+ int *val)
+{
+ int dow = dateFmt->date.dayOfWeek;
+
+ if (*tok->tokWord.start == 'U') {
+ if (dow == 7) {
+ dow = 0;
+ }
+ dow++;
+ }
+ *val = ( dateFmt->date.dayOfYear - dow + 7 ) / 7;
+ return TCL_OK;
+}
+static int
+ClockFmtToken_JDN_Proc(
+ TCL_UNUSED(ClockFmtScnCmdArgs *),
+ DateFormat *dateFmt,
+ ClockFormatToken *tok,
+ TCL_UNUSED(int *))
+{
+ Tcl_WideInt intJD = dateFmt->date.julianDay;
+ int fractJD;
+
+ /* Convert to JDN parts (regarding start offset) and time fraction */
+ fractJD = dateFmt->date.secondOfDay
+ - (int)tok->map->offs; /* 0 for calendar or 43200 for astro JD */
+ if (fractJD < 0) {
+ intJD--;
+ fractJD += SECONDS_PER_DAY;
+ }
+ if (fractJD && intJD < 0) { /* avoid jump over 0, by negative JD's */
+ intJD++;
+ if (intJD == 0) {
+ /* -0.0 / -0.9 has zero integer part, so append "-" extra */
+ if (FrmResultAllocate(dateFmt, 1) != TCL_OK) { return TCL_ERROR; };
+ *dateFmt->output++ = '-';
+ }
+ /* and inverse seconds of day, -0(75) -> -0.25 as float */
+ fractJD = SECONDS_PER_DAY - fractJD;
+ }
+
+ /* 21 is max width of (negative) wide-int (rather smaller, but anyway a time fraction below) */
+ if (FrmResultAllocate(dateFmt, 21) != TCL_OK) { return TCL_ERROR; };
+ dateFmt->output = _witoaw(dateFmt->output, intJD, '0', 1);
+ /* simplest cases .0 and .5 */
+ if (!fractJD || fractJD == (SECONDS_PER_DAY / 2)) {
+ /* point + 0 or 5 */
+ if (FrmResultAllocate(dateFmt, 1+1) != TCL_OK) { return TCL_ERROR; };
+ *dateFmt->output++ = '.';
+ *dateFmt->output++ = !fractJD ? '0' : '5';
+ *dateFmt->output = '\0';
+ return TCL_OK;
+ } else {
+ /* wrap the time fraction */
+ #define JDN_MAX_PRECISION 8
+ #define JDN_MAX_PRECBOUND 100000000 /* 10**JDN_MAX_PRECISION */
+ char *p;
+
+ /* to float (part after floating point, + 0.5 to round it up) */
+ fractJD = (int)(
+ (double)fractJD * JDN_MAX_PRECBOUND / SECONDS_PER_DAY + 0.5
+ );
+ /* point + integer (as time fraction after floating point) */
+ if (FrmResultAllocate(dateFmt, 1+JDN_MAX_PRECISION) != TCL_OK) { return TCL_ERROR; };
+ *dateFmt->output++ = '.';
+ p = _itoaw(dateFmt->output, fractJD, '0', JDN_MAX_PRECISION);
+ /* remove trailing zero's */
+ dateFmt->output++;
+ while (p > dateFmt->output && *(p-1) == '0') {p--;}
+ *p = '\0';
+ dateFmt->output = p;
+ }
+ return TCL_OK;
+}
+static int
+ClockFmtToken_TimeZone_Proc(
+ ClockFmtScnCmdArgs *opts,
+ DateFormat *dateFmt,
+ ClockFormatToken *tok,
+ TCL_UNUSED(int *))
+{
+ if (*tok->tokWord.start == 'z') {
+ int z = dateFmt->date.tzOffset;
+ char sign = '+';
+
+ if ( z < 0 ) {
+ z = -z;
+ sign = '-';
+ }
+ if (FrmResultAllocate(dateFmt, 7) != TCL_OK) { return TCL_ERROR; };
+ *dateFmt->output++ = sign;
+ dateFmt->output = _itoaw(dateFmt->output, z / 3600, '0', 2);
+ z %= 3600;
+ dateFmt->output = _itoaw(dateFmt->output, z / 60, '0', 2);
+ z %= 60;
+ if (z != 0) {
+ dateFmt->output = _itoaw(dateFmt->output, z, '0', 2);
+ }
+ } else {
+ Tcl_Obj * objPtr;
+ const char *s; Tcl_Size len;
+ /* convert seconds to local seconds to obtain tzName object */
+ if (ConvertUTCToLocal(opts->clientData, opts->interp,
+ &dateFmt->date, opts->timezoneObj,
+ GREGORIAN_CHANGE_DATE) != TCL_OK) {
+ return TCL_ERROR;
+ };
+ objPtr = dateFmt->date.tzName;
+ s = TclGetStringFromObj(objPtr, &len);
+ if (FrmResultAllocate(dateFmt, len) != TCL_OK) { return TCL_ERROR; };
+ memcpy(dateFmt->output, s, len + 1);
+ dateFmt->output += len;
+ }
+ return TCL_OK;
+}
+
+static int
+ClockFmtToken_LocaleERA_Proc(
+ ClockFmtScnCmdArgs *opts,
+ DateFormat *dateFmt,
+ TCL_UNUSED(ClockFormatToken *),
+ TCL_UNUSED(int *))
+{
+ Tcl_Obj *mcObj;
+ const char *s;
+ Tcl_Size len;
+
+ if (dateFmt->date.isBce) {
+ mcObj = ClockMCGet(opts, MCLIT_BCE);
+ } else {
+ mcObj = ClockMCGet(opts, MCLIT_CE);
+ }
+ if (mcObj == NULL) {
+ return TCL_ERROR;
+ }
+ s = TclGetStringFromObj(mcObj, &len);
+ if (FrmResultAllocate(dateFmt, len) != TCL_OK) { return TCL_ERROR; };
+ memcpy(dateFmt->output, s, len + 1);
+ dateFmt->output += len;
+
+ return TCL_OK;
+}
+
+static int
+ClockFmtToken_LocaleERAYear_Proc(
+ ClockFmtScnCmdArgs *opts,
+ DateFormat *dateFmt,
+ ClockFormatToken *tok,
+ int *val)
+{
+ Tcl_Size rowc;
+ Tcl_Obj **rowv;
+
+ if (dateFmt->localeEra == NULL) {
+ Tcl_Obj *mcObj = ClockMCGet(opts, MCLIT_LOCALE_ERAS);
+ if (mcObj == NULL) {
+ return TCL_ERROR;
+ }
+ if (TclListObjGetElements(opts->interp, mcObj, &rowc, &rowv) != TCL_OK) {
+ return TCL_ERROR;
+ }
+ if (rowc != 0) {
+ dateFmt->localeEra = LookupLastTransition(opts->interp,
+ dateFmt->date.localSeconds, rowc, rowv, NULL);
+ }
+ if (dateFmt->localeEra == NULL) {
+ dateFmt->localeEra = (Tcl_Obj*)1;
+ }
+ }
+
+ /* if no LOCALE_ERAS in catalog or era not found */
+ if (dateFmt->localeEra == (Tcl_Obj*)1) {
+ if (FrmResultAllocate(dateFmt, 11) != TCL_OK) { return TCL_ERROR; };
+ if (*tok->tokWord.start == 'C') { /* %EC */
+ *val = dateFmt->date.year / 100;
+ dateFmt->output = _itoaw(dateFmt->output,
+ *val, '0', 2);
+ } else { /* %Ey */
+ *val = dateFmt->date.year % 100;
+ dateFmt->output = _itoaw(dateFmt->output,
+ *val, '0', 2);
+ }
+ } else {
+ Tcl_Obj *objPtr;
+ const char *s;
+ Tcl_Size len;
+ if (*tok->tokWord.start == 'C') { /* %EC */
+ if (Tcl_ListObjIndex(opts->interp, dateFmt->localeEra, 1,
+ &objPtr) != TCL_OK ) {
+ return TCL_ERROR;
+ }
+ } else { /* %Ey */
+ if (Tcl_ListObjIndex(opts->interp, dateFmt->localeEra, 2,
+ &objPtr) != TCL_OK ) {
+ return TCL_ERROR;
+ }
+ if (Tcl_GetIntFromObj(opts->interp, objPtr, val) != TCL_OK) {
+ return TCL_ERROR;
+ }
+ *val = dateFmt->date.year - *val;
+ /* if year in locale numerals */
+ if (*val >= 0 && *val < 100) {
+ /* year as integer */
+ Tcl_Obj * mcObj = ClockMCGet(opts, MCLIT_LOCALE_NUMERALS);
+ if (mcObj == NULL) {
+ return TCL_ERROR;
+ }
+ if (Tcl_ListObjIndex(opts->interp, mcObj, *val, &objPtr) != TCL_OK) {
+ return TCL_ERROR;
+ }
+ } else {
+ /* year as integer */
+ if (FrmResultAllocate(dateFmt, 11) != TCL_OK) { return TCL_ERROR; };
+ dateFmt->output = _itoaw(dateFmt->output,
+ *val, '0', 2);
+ return TCL_OK;
+ }
+ }
+ s = TclGetStringFromObj(objPtr, &len);
+ if (FrmResultAllocate(dateFmt, len) != TCL_OK) { return TCL_ERROR; };
+ memcpy(dateFmt->output, s, len + 1);
+ dateFmt->output += len;
+ }
+ return TCL_OK;
+}
+
+
+static const char *FmtSTokenMapIndex =
+ "demNbByYCHMSIklpaAuwUVzgGjJsntQ";
+static ClockFormatTokenMap FmtSTokenMap[] = {
+ /* %d */
+ {CTOKT_INT, "0", 2, 0, 0, 0, offsetof(DateFormat, date.dayOfMonth), NULL, NULL},
+ /* %e */
+ {CTOKT_INT, " ", 2, 0, 0, 0, offsetof(DateFormat, date.dayOfMonth), NULL, NULL},
+ /* %m */
+ {CTOKT_INT, "0", 2, 0, 0, 0, offsetof(DateFormat, date.month), NULL, NULL},
+ /* %N */
+ {CTOKT_INT, " ", 2, 0, 0, 0, offsetof(DateFormat, date.month), NULL, NULL},
+ /* %b %h */
+ {CTOKT_INT, NULL, 0, CLFMT_LOCALE_INDX | CLFMT_DECR, 0, 12, offsetof(DateFormat, date.month),
+ NULL, (void *)MCLIT_MONTHS_ABBREV},
+ /* %B */
+ {CTOKT_INT, NULL, 0, CLFMT_LOCALE_INDX | CLFMT_DECR, 0, 12, offsetof(DateFormat, date.month),
+ NULL, (void *)MCLIT_MONTHS_FULL},
+ /* %y */
+ {CTOKT_INT, "0", 2, 0, 0, 100, offsetof(DateFormat, date.year), NULL, NULL},
+ /* %Y */
+ {CTOKT_INT, "0", 4, 0, 0, 0, offsetof(DateFormat, date.year), NULL, NULL},
+ /* %C */
+ {CTOKT_INT, "0", 2, 0, 100, 0, offsetof(DateFormat, date.year), NULL, NULL},
+ /* %H */
+ {CTOKT_INT, "0", 2, 0, 3600, 24, offsetof(DateFormat, date.secondOfDay), NULL, NULL},
+ /* %M */
+ {CTOKT_INT, "0", 2, 0, 60, 60, offsetof(DateFormat, date.secondOfDay), NULL, NULL},
+ /* %S */
+ {CTOKT_INT, "0", 2, 0, 0, 60, offsetof(DateFormat, date.secondOfDay), NULL, NULL},
+ /* %I */
+ {CTOKT_INT, "0", 2, CLFMT_CALC, 0, 0, offsetof(DateFormat, date.secondOfDay),
+ ClockFmtToken_HourAMPM_Proc, NULL},
+ /* %k */
+ {CTOKT_INT, " ", 2, 0, 3600, 24, offsetof(DateFormat, date.secondOfDay), NULL, NULL},
+ /* %l */
+ {CTOKT_INT, " ", 2, CLFMT_CALC, 0, 0, offsetof(DateFormat, date.secondOfDay),
+ ClockFmtToken_HourAMPM_Proc, NULL},
+ /* %p %P */
+ {CTOKT_INT, NULL, 0, 0, 0, 0, offsetof(DateFormat, date.secondOfDay),
+ ClockFmtToken_AMPM_Proc, NULL},
+ /* %a */
+ {CTOKT_INT, NULL, 0, CLFMT_LOCALE_INDX, 0, 7, offsetof(DateFormat, date.dayOfWeek),
+ NULL, (void *)MCLIT_DAYS_OF_WEEK_ABBREV},
+ /* %A */
+ {CTOKT_INT, NULL, 0, CLFMT_LOCALE_INDX, 0, 7, offsetof(DateFormat, date.dayOfWeek),
+ NULL, (void *)MCLIT_DAYS_OF_WEEK_FULL},
+ /* %u */
+ {CTOKT_INT, " ", 1, 0, 0, 0, offsetof(DateFormat, date.dayOfWeek), NULL, NULL},
+ /* %w */
+ {CTOKT_INT, " ", 1, 0, 0, 7, offsetof(DateFormat, date.dayOfWeek), NULL, NULL},
+ /* %U %W */
+ {CTOKT_INT, "0", 2, CLFMT_CALC, 0, 0, offsetof(DateFormat, date.dayOfYear),
+ ClockFmtToken_WeekOfYear_Proc, NULL},
+ /* %V */
+ {CTOKT_INT, "0", 2, 0, 0, 0, offsetof(DateFormat, date.iso8601Week), NULL, NULL},
+ /* %z %Z */
+ {CFMTT_PROC, NULL, 0, 0, 0, 0, 0,
+ ClockFmtToken_TimeZone_Proc, NULL},
+ /* %g */
+ {CTOKT_INT, "0", 2, 0, 0, 100, offsetof(DateFormat, date.iso8601Year), NULL, NULL},
+ /* %G */
+ {CTOKT_INT, "0", 4, 0, 0, 0, offsetof(DateFormat, date.iso8601Year), NULL, NULL},
+ /* %j */
+ {CTOKT_INT, "0", 3, 0, 0, 0, offsetof(DateFormat, date.dayOfYear), NULL, NULL},
+ /* %J */
+ {CTOKT_WIDE, "0", 7, 0, 0, 0, offsetof(DateFormat, date.julianDay), NULL, NULL},
+ /* %s */
+ {CTOKT_WIDE, "0", 1, 0, 0, 0, offsetof(DateFormat, date.seconds), NULL, NULL},
+ /* %n */
+ {CTOKT_CHAR, "\n", 0, 0, 0, 0, 0, NULL, NULL},
+ /* %t */
+ {CTOKT_CHAR, "\t", 0, 0, 0, 0, 0, NULL, NULL},
+ /* %Q */
+ {CFMTT_PROC, NULL, 0, 0, 0, 0, 0,
+ ClockFmtToken_StarDate_Proc, NULL},
+};
+static const char *FmtSTokenMapAliasIndex[2] = {
+ "hPWZ",
+ "bpUz"
+};
+
+static const char *FmtETokenMapIndex =
+ "EJjys";
+static ClockFormatTokenMap FmtETokenMap[] = {
+ /* %EE */
+ {CFMTT_PROC, NULL, 0, 0, 0, 0, 0,
+ ClockFmtToken_LocaleERA_Proc, NULL},
+ /* %EJ */
+ {CFMTT_PROC, NULL, 0, 0, 0, 0, 0, /* calendar JDN starts at midnight */
+ ClockFmtToken_JDN_Proc, NULL},
+ /* %Ej */
+ {CFMTT_PROC, NULL, 0, 0, 0, 0, (SECONDS_PER_DAY/2), /* astro JDN starts at noon */
+ ClockFmtToken_JDN_Proc, NULL},
+ /* %Ey %EC */
+ {CTOKT_INT, NULL, 0, 0, 0, 0, offsetof(DateFormat, date.year),
+ ClockFmtToken_LocaleERAYear_Proc, NULL},
+ /* %Es */
+ {CTOKT_WIDE, "0", 1, 0, 0, 0, offsetof(DateFormat, date.localSeconds), NULL, NULL},
+};
+static const char *FmtETokenMapAliasIndex[2] = {
+ "C",
+ "y"
+};
+
+static const char *FmtOTokenMapIndex =
+ "dmyHIMSuw";
+static ClockFormatTokenMap FmtOTokenMap[] = {
+ /* %Od %Oe */
+ {CTOKT_INT, NULL, 0, CLFMT_LOCALE_INDX, 0, 100, offsetof(DateFormat, date.dayOfMonth),
+ NULL, (void *)MCLIT_LOCALE_NUMERALS},
+ /* %Om */
+ {CTOKT_INT, NULL, 0, CLFMT_LOCALE_INDX, 0, 100, offsetof(DateFormat, date.month),
+ NULL, (void *)MCLIT_LOCALE_NUMERALS},
+ /* %Oy */
+ {CTOKT_INT, NULL, 0, CLFMT_LOCALE_INDX, 0, 100, offsetof(DateFormat, date.year),
+ NULL, (void *)MCLIT_LOCALE_NUMERALS},
+ /* %OH %Ok */
+ {CTOKT_INT, NULL, 0, CLFMT_LOCALE_INDX, 3600, 24, offsetof(DateFormat, date.secondOfDay),
+ NULL, (void *)MCLIT_LOCALE_NUMERALS},
+ /* %OI %Ol */
+ {CTOKT_INT, NULL, 0, CLFMT_CALC | CLFMT_LOCALE_INDX, 0, 0, offsetof(DateFormat, date.secondOfDay),
+ ClockFmtToken_HourAMPM_Proc, (void *)MCLIT_LOCALE_NUMERALS},
+ /* %OM */
+ {CTOKT_INT, NULL, 0, CLFMT_LOCALE_INDX, 60, 60, offsetof(DateFormat, date.secondOfDay),
+ NULL, (void *)MCLIT_LOCALE_NUMERALS},
+ /* %OS */
+ {CTOKT_INT, NULL, 0, CLFMT_LOCALE_INDX, 0, 60, offsetof(DateFormat, date.secondOfDay),
+ NULL, (void *)MCLIT_LOCALE_NUMERALS},
+ /* %Ou */
+ {CTOKT_INT, NULL, 0, CLFMT_LOCALE_INDX, 0, 100, offsetof(DateFormat, date.dayOfWeek),
+ NULL, (void *)MCLIT_LOCALE_NUMERALS},
+ /* %Ow */
+ {CTOKT_INT, NULL, 0, CLFMT_LOCALE_INDX, 0, 7, offsetof(DateFormat, date.dayOfWeek),
+ NULL, (void *)MCLIT_LOCALE_NUMERALS},
+};
+static const char *FmtOTokenMapAliasIndex[2] = {
+ "ekl",
+ "dHI"
+};
+
+static ClockFormatTokenMap FmtWordTokenMap = {
+ CTOKT_WORD, NULL, 0, 0, 0, 0, 0, NULL, NULL
+};
+
+/*
+ *----------------------------------------------------------------------
+ */
+ClockFmtScnStorage *
+ClockGetOrParseFmtFormat(
+ Tcl_Interp *interp, /* Tcl interpreter */
+ Tcl_Obj *formatObj) /* Format container */
+{
+ ClockFmtScnStorage *fss;
+
+ fss = Tcl_GetClockFrmScnFromObj(interp, formatObj);
+ if (fss == NULL) {
+ return NULL;
+ }
+
+ /* if format (fmtTok) already tokenized */
+ if (fss->fmtTok != NULL) {
+ return fss;
+ }
+
+ Tcl_MutexLock(&ClockFmtMutex);
+
+ /* first time formatting - tokenize format */
+ if (fss->fmtTok == NULL) {
+ ClockFormatToken *tok, *fmtTok;
+ unsigned int tokCnt;
+ const char *p, *e, *cp;
+
+ e = p = HashEntry4FmtScn(fss)->key.string;
+ e += strlen(p);
+
+ /* estimate token count by % char and format length */
+ fss->fmtTokC = EstimateTokenCount(p, e);
+
+ fmtTok = tok = (ClockFormatToken *)Tcl_Alloc(sizeof(*tok) * fss->fmtTokC);
+ memset(tok, 0, sizeof(*(tok)));
+ tokCnt = 1;
+ while (p < e) {
+ switch (*p) {
+ case '%':
+ if (1) {
+ ClockFormatTokenMap * fmtMap = FmtSTokenMap;
+ const char *mapIndex = FmtSTokenMapIndex,
+ **aliasIndex = FmtSTokenMapAliasIndex;
+ if (p+1 >= e) {
+ goto word_tok;
+ }
+ p++;
+ /* try to find modifier: */
+ switch (*p) {
+ case '%':
+ /* begin new word token - don't join with previous word token,
+ * because current mapping should be "...%%..." -> "...%..." */
+ tok->map = &FmtWordTokenMap;
+ tok->tokWord.start = p;
+ tok->tokWord.end = p+1;
+ AllocTokenInChain(tok, fmtTok, fss->fmtTokC, ClockFormatToken *); tokCnt++;
+ p++;
+ continue;
+ break;
+ case 'E':
+ fmtMap = FmtETokenMap,
+ mapIndex = FmtETokenMapIndex,
+ aliasIndex = FmtETokenMapAliasIndex;
+ p++;
+ break;
+ case 'O':
+ fmtMap = FmtOTokenMap,
+ mapIndex = FmtOTokenMapIndex,
+ aliasIndex = FmtOTokenMapAliasIndex;
+ p++;
+ break;
+ }
+ /* search direct index */
+ cp = strchr(mapIndex, *p);
+ if (!cp || *cp == '\0') {
+ /* search wrapper index (multiple chars for same token) */
+ cp = strchr(aliasIndex[0], *p);
+ if (!cp || *cp == '\0') {
+ p--; if (fmtMap != FmtSTokenMap) p--;
+ goto word_tok;
+ }
+ cp = strchr(mapIndex, aliasIndex[1][cp - aliasIndex[0]]);
+ if (!cp || *cp == '\0') { /* unexpected, but ... */
+ #ifdef DEBUG
+ Tcl_Panic("token \"%c\" has no map in wrapper resolver", *p);
+ #endif
+ p--; if (fmtMap != FmtSTokenMap) p--;
+ goto word_tok;
+ }
+ }
+ tok->map = &fmtMap[cp - mapIndex];
+ tok->tokWord.start = p;
+ /* next token */
+ AllocTokenInChain(tok, fmtTok, fss->fmtTokC, ClockFormatToken *); tokCnt++;
+ p++;
+ continue;
+ }
+ break;
+ default:
+word_tok:
+ if (1) {
+ ClockFormatToken *wordTok = tok;
+ if (tok > fmtTok && (tok-1)->map == &FmtWordTokenMap) {
+ wordTok = tok-1;
+ }
+ if (wordTok == tok) {
+ wordTok->tokWord.start = p;
+ wordTok->map = &FmtWordTokenMap;
+ AllocTokenInChain(tok, fmtTok, fss->fmtTokC, ClockFormatToken *); tokCnt++;
+ }
+ p = Tcl_UtfNext(p);
+ wordTok->tokWord.end = p;
+ }
+ break;
+ }
+ }
+
+ /* correct count of real used tokens and free mem if desired
+ * (1 is acceptable delta to prevent memory fragmentation) */
+ if (fss->fmtTokC > tokCnt + (CLOCK_MIN_TOK_CHAIN_BLOCK_SIZE / 2)) {
+ if ( (tok = (ClockFormatToken *)Tcl_Realloc(fmtTok, tokCnt * sizeof(*tok))) != NULL ) {
+ fmtTok = tok;
+ }
+ }
+
+ /* now we're ready - assign now to storage (note the threaded race condition) */
+ fss->fmtTok = fmtTok;
+ fss->fmtTokC = tokCnt;
+ }
+done:
+ Tcl_MutexUnlock(&ClockFmtMutex);
+
+ return fss;
+}
+
+/*
+ *----------------------------------------------------------------------
+ */
+int
+ClockFormat(
+ DateFormat *dateFmt, /* Date fields used for parsing & converting */
+ ClockFmtScnCmdArgs *opts) /* Command options */
+{
+ ClockFmtScnStorage *fss;
+ ClockFormatToken *tok;
+ ClockFormatTokenMap *map;
+ char resMem[MIN_FMT_RESULT_BLOCK_ALLOC];
+
+ /* get localized format */
+ if (ClockLocalizeFormat(opts) == NULL) {
+ return TCL_ERROR;
+ }
+
+ if ( !(fss = ClockGetOrParseFmtFormat(opts->interp, opts->formatObj))
+ || !(tok = fss->fmtTok)
+ ) {
+ return TCL_ERROR;
+ }
+
+ /* result container object */
+ dateFmt->resMem = resMem;
+ dateFmt->resEnd = dateFmt->resMem + sizeof(resMem);
+ if (fss->fmtMinAlloc > sizeof(resMem)) {
+ dateFmt->resMem = (char *)Tcl_Alloc(fss->fmtMinAlloc);
+ dateFmt->resEnd = dateFmt->resMem + fss->fmtMinAlloc;
+ if (dateFmt->resMem == NULL) {
+ return TCL_ERROR;
+ }
+ }
+ dateFmt->output = dateFmt->resMem;
+ *dateFmt->output = '\0';
+
+ /* do format each token */
+ for (; tok->map != NULL; tok++) {
+ map = tok->map;
+ switch (map->type)
+ {
+ case CTOKT_INT:
+ if (1) {
+ int val = (int)*(int *)(((char *)dateFmt) + map->offs);
+ if (map->fmtproc == NULL) {
+ if (map->flags & CLFMT_DECR) {
+ val--;
+ }
+ if (map->flags & CLFMT_INCR) {
+ val++;
+ }
+ if (map->divider) {
+ val /= map->divider;
+ }
+ if (map->divmod) {
+ val %= map->divmod;
+ }
+ } else {
+ if (map->fmtproc(opts, dateFmt, tok, &val) != TCL_OK) {
+ goto done;
+ }
+ /* if not calculate only (output inside fmtproc) */
+ if (!(map->flags & CLFMT_CALC)) {
+ continue;
+ }
+ }
+ if (!(map->flags & CLFMT_LOCALE_INDX)) {
+ if (FrmResultAllocate(dateFmt, 11) != TCL_OK) { goto error; };
+ if (map->width) {
+ dateFmt->output = _itoaw(dateFmt->output, val, *map->tostr, map->width);
+ } else {
+ dateFmt->output += sprintf(dateFmt->output, map->tostr, val);
+ }
+ } else {
+ const char *s;
+ Tcl_Obj * mcObj = ClockMCGet(opts, PTR2INT(map->data) /* mcKey */);
+ if (mcObj == NULL) {
+ goto error;
+ }
+ if ( Tcl_ListObjIndex(opts->interp, mcObj, val, &mcObj) != TCL_OK
+ || mcObj == NULL
+ ) {
+ goto error;
+ }
+ s = TclGetString(mcObj);
+ if (FrmResultAllocate(dateFmt, mcObj->length) != TCL_OK) { goto error; };
+ memcpy(dateFmt->output, s, mcObj->length + 1);
+ dateFmt->output += mcObj->length;
+ }
+ }
+ break;
+ case CTOKT_WIDE:
+ if (1) {
+ Tcl_WideInt val = *(Tcl_WideInt *)(((char *)dateFmt) + map->offs);
+ if (FrmResultAllocate(dateFmt, 21) != TCL_OK) { goto error; };
+ if (map->width) {
+ dateFmt->output = _witoaw(dateFmt->output, val, *map->tostr, map->width);
+ } else {
+ dateFmt->output += sprintf(dateFmt->output, map->tostr, val);
+ }
+ }
+ break;
+ case CTOKT_CHAR:
+ if (FrmResultAllocate(dateFmt, 1) != TCL_OK) { goto error; };
+ *dateFmt->output++ = *map->tostr;
+ break;
+ case CFMTT_PROC:
+ if (map->fmtproc(opts, dateFmt, tok, NULL) != TCL_OK) {
+ goto error;
+ };
+ break;
+ case CTOKT_WORD:
+ if (1) {
+ Tcl_Size len = tok->tokWord.end - tok->tokWord.start;
+ if (FrmResultAllocate(dateFmt, len) != TCL_OK) { goto error; };
+ if (len == 1) {
+ *dateFmt->output++ = *tok->tokWord.start;
+ } else {
+ memcpy(dateFmt->output, tok->tokWord.start, len);
+ dateFmt->output += len;
+ }
+ }
+ break;
+ }
+ }
+
+ goto done;
+
+error:
+
+ if (dateFmt->resMem != resMem) {
+ Tcl_Free(dateFmt->resMem);
+ }
+ dateFmt->resMem = NULL;
+
+done:
+
+ if (dateFmt->resMem) {
+ size_t size;
+ Tcl_Obj *result;
+ TclNewObj(result);
+ result->length = dateFmt->output - dateFmt->resMem;
+ size = result->length+1;
+ if (dateFmt->resMem == resMem) {
+ result->bytes = (char *)Tcl_Alloc(size);
+ if (result->bytes == NULL) {
+ return TCL_ERROR;
+ }
+ memcpy(result->bytes, dateFmt->resMem, size);
+ } else if ((dateFmt->resEnd - dateFmt->resMem) / size > MAX_FMT_RESULT_THRESHOLD) {
+ result->bytes = (char *)Tcl_Realloc(dateFmt->resMem, size);
+ if (result->bytes == NULL) {
+ result->bytes = dateFmt->resMem;
+ }
+ } else {
+ result->bytes = dateFmt->resMem;
+ }
+ /* save last used buffer length */
+ if ( dateFmt->resMem != resMem
+ && fss->fmtMinAlloc < size + MIN_FMT_RESULT_BLOCK_DELTA
+ ) {
+ fss->fmtMinAlloc = size + MIN_FMT_RESULT_BLOCK_DELTA;
+ }
+ result->bytes[result->length] = '\0';
+ Tcl_SetObjResult(opts->interp, result);
+ return TCL_OK;
+ }
+
+ return TCL_ERROR;
+}
+
+
+void
+ClockFrmScnClearCaches(void)
+{
+ Tcl_MutexLock(&ClockFmtMutex);
+ /* clear caches ... */
+ Tcl_MutexUnlock(&ClockFmtMutex);
+}
+
+static void
+ClockFrmScnFinalize(
+ TCL_UNUSED(void *))
+{
+ Tcl_MutexLock(&ClockFmtMutex);
+#if CLOCK_FMT_SCN_STORAGE_GC_SIZE > 0
+ /* clear GC */
+ ClockFmtScnStorage_GC.stackPtr = NULL;
+ ClockFmtScnStorage_GC.stackBound = NULL;
+ ClockFmtScnStorage_GC.count = 0;
+#endif
+ if (initialized) {
+ Tcl_DeleteHashTable(&FmtScnHashTable);
+ initialized = 0;
+ }
+ Tcl_MutexUnlock(&ClockFmtMutex);
+ Tcl_MutexFinalize(&ClockFmtMutex);
+}
+/*
+ * Local Variables:
+ * mode: c
+ * c-basic-offset: 4
+ * fill-column: 78
+ * End:
+ */
diff --git a/generic/tclDate.c b/generic/tclDate.c
index 8c048a6..e419585 100644
--- a/generic/tclDate.c
+++ b/generic/tclDate.c
@@ -78,8 +78,9 @@
* This file is generated from a yacc grammar defined in the file
* tclGetDate.y. It should not be edited directly.
*
- * Copyright (c) 1992-1995 Karl Lehenbauer & Mark Diekhans.
- * Copyright (c) 1995-1997 Sun Microsystems, Inc.
+ * Copyright © 1992-1995 Karl Lehenbauer & Mark Diekhans.
+ * Copyright © 1995-1997 Sun Microsystems, Inc.
+ * Copyright © 2015 Sergey G. Brester aka sebres.
*
* See the file "license.terms" for information on usage and redistribution of
* this file, and for a DISCLAIMER OF ALL WARRANTIES.
@@ -96,86 +97,20 @@
#pragma warning( disable : 4102 )
#endif /* _MSC_VER */
-/*
- * Meridian: am, pm, or 24-hour style.
- */
-
-typedef enum _MERIDIAN {
- MERam, MERpm, MER24
-} MERIDIAN;
+#if 0
+#define YYDEBUG 1
+#endif
/*
* yyparse will accept a 'struct DateInfo' as its parameter; that's where the
* parsed fields will be returned.
*/
-typedef struct DateInfo {
-
- Tcl_Obj* messages; /* Error messages */
- const char* separatrix; /* String separating messages */
-
- time_t dateYear;
- time_t dateMonth;
- time_t dateDay;
- int dateHaveDate;
-
- time_t dateHour;
- time_t dateMinutes;
- time_t dateSeconds;
- MERIDIAN dateMeridian;
- int dateHaveTime;
-
- time_t dateTimezone;
- int dateDSTmode;
- int dateHaveZone;
-
- time_t dateRelMonth;
- time_t dateRelDay;
- time_t dateRelSeconds;
- int dateHaveRel;
-
- time_t dateMonthOrdinal;
- int dateHaveOrdinalMonth;
-
- time_t dateDayOrdinal;
- time_t dateDayNumber;
- int dateHaveDay;
-
- const char *dateStart;
- const char *dateInput;
- time_t *dateRelPointer;
-
- int dateDigitCount;
-} DateInfo;
+#include "tclDate.h"
#define YYMALLOC Tcl_Alloc
#define YYFREE(x) (Tcl_Free((void*) (x)))
-#define yyDSTmode (info->dateDSTmode)
-#define yyDayOrdinal (info->dateDayOrdinal)
-#define yyDayNumber (info->dateDayNumber)
-#define yyMonthOrdinal (info->dateMonthOrdinal)
-#define yyHaveDate (info->dateHaveDate)
-#define yyHaveDay (info->dateHaveDay)
-#define yyHaveOrdinalMonth (info->dateHaveOrdinalMonth)
-#define yyHaveRel (info->dateHaveRel)
-#define yyHaveTime (info->dateHaveTime)
-#define yyHaveZone (info->dateHaveZone)
-#define yyTimezone (info->dateTimezone)
-#define yyDay (info->dateDay)
-#define yyMonth (info->dateMonth)
-#define yyYear (info->dateYear)
-#define yyHour (info->dateHour)
-#define yyMinutes (info->dateMinutes)
-#define yySeconds (info->dateSeconds)
-#define yyMeridian (info->dateMeridian)
-#define yyRelMonth (info->dateRelMonth)
-#define yyRelDay (info->dateRelDay)
-#define yyRelSeconds (info->dateRelSeconds)
-#define yyRelPointer (info->dateRelPointer)
-#define yyInput (info->dateInput)
-#define yyDigitCount (info->dateDigitCount)
-
#define EPOCH 1970
#define START_OF_TIME 1902
#define END_OF_TIME 2037
@@ -187,18 +122,24 @@ typedef struct DateInfo {
#define TM_YEAR_BASE 1900
-#define HOUR(x) ((int) (60 * (x)))
-#define SECSPERDAY (24L * 60L * 60L)
+#define HOUR(x) ((60 * (int)(x)))
#define IsLeapYear(x) (((x) % 4 == 0) && ((x) % 100 != 0 || (x) % 400 == 0))
+#define yyIncrFlags(f) \
+ do { \
+ info->errFlags |= (info->flags & (f)); \
+ if (info->errFlags) { YYABORT; } \
+ info->flags |= (f); \
+ } while (0);
+
/*
* An entry in the lexical lookup table.
*/
-typedef struct _TABLE {
+typedef struct {
const char *name;
int type;
- time_t value;
+ int value;
} TABLE;
/*
@@ -259,14 +200,18 @@ extern int TclDatedebug;
tMONTH_UNIT = 264, /* tMONTH_UNIT */
tSTARDATE = 265, /* tSTARDATE */
tSEC_UNIT = 266, /* tSEC_UNIT */
- tSNUMBER = 267, /* tSNUMBER */
- tUNUMBER = 268, /* tUNUMBER */
- tZONE = 269, /* tZONE */
- tEPOCH = 270, /* tEPOCH */
- tDST = 271, /* tDST */
- tISOBASE = 272, /* tISOBASE */
- tDAY_UNIT = 273, /* tDAY_UNIT */
- tNEXT = 274 /* tNEXT */
+ tUNUMBER = 267, /* tUNUMBER */
+ tZONE = 268, /* tZONE */
+ tZONEwO4 = 269, /* tZONEwO4 */
+ tZONEwO2 = 270, /* tZONEwO2 */
+ tEPOCH = 271, /* tEPOCH */
+ tDST = 272, /* tDST */
+ tISOBAS8 = 273, /* tISOBAS8 */
+ tISOBAS6 = 274, /* tISOBAS6 */
+ tISOBASL = 275, /* tISOBASL */
+ tDAY_UNIT = 276, /* tDAY_UNIT */
+ tNEXT = 277, /* tNEXT */
+ SP = 278 /* SP */
};
typedef enum yytokentype yytoken_kind_t;
#endif
@@ -276,7 +221,7 @@ extern int TclDatedebug;
union YYSTYPE
{
- time_t Number;
+ long long Number;
enum _MERIDIAN Meridian;
@@ -323,36 +268,48 @@ enum yysymbol_kind_t
YYSYMBOL_tMONTH_UNIT = 9, /* tMONTH_UNIT */
YYSYMBOL_tSTARDATE = 10, /* tSTARDATE */
YYSYMBOL_tSEC_UNIT = 11, /* tSEC_UNIT */
- YYSYMBOL_tSNUMBER = 12, /* tSNUMBER */
- YYSYMBOL_tUNUMBER = 13, /* tUNUMBER */
- YYSYMBOL_tZONE = 14, /* tZONE */
- YYSYMBOL_tEPOCH = 15, /* tEPOCH */
- YYSYMBOL_tDST = 16, /* tDST */
- YYSYMBOL_tISOBASE = 17, /* tISOBASE */
- YYSYMBOL_tDAY_UNIT = 18, /* tDAY_UNIT */
- YYSYMBOL_tNEXT = 19, /* tNEXT */
- YYSYMBOL_20_ = 20, /* ':' */
- YYSYMBOL_21_ = 21, /* ',' */
- YYSYMBOL_22_ = 22, /* '/' */
- YYSYMBOL_23_ = 23, /* '-' */
- YYSYMBOL_24_ = 24, /* '.' */
- YYSYMBOL_25_ = 25, /* '+' */
- YYSYMBOL_YYACCEPT = 26, /* $accept */
- YYSYMBOL_spec = 27, /* spec */
- YYSYMBOL_item = 28, /* item */
- YYSYMBOL_time = 29, /* time */
- YYSYMBOL_zone = 30, /* zone */
- YYSYMBOL_day = 31, /* day */
- YYSYMBOL_date = 32, /* date */
- YYSYMBOL_ordMonth = 33, /* ordMonth */
- YYSYMBOL_iso = 34, /* iso */
- YYSYMBOL_trek = 35, /* trek */
- YYSYMBOL_relspec = 36, /* relspec */
- YYSYMBOL_relunits = 37, /* relunits */
- YYSYMBOL_sign = 38, /* sign */
- YYSYMBOL_unit = 39, /* unit */
- YYSYMBOL_number = 40, /* number */
- YYSYMBOL_o_merid = 41 /* o_merid */
+ YYSYMBOL_tUNUMBER = 12, /* tUNUMBER */
+ YYSYMBOL_tZONE = 13, /* tZONE */
+ YYSYMBOL_tZONEwO4 = 14, /* tZONEwO4 */
+ YYSYMBOL_tZONEwO2 = 15, /* tZONEwO2 */
+ YYSYMBOL_tEPOCH = 16, /* tEPOCH */
+ YYSYMBOL_tDST = 17, /* tDST */
+ YYSYMBOL_tISOBAS8 = 18, /* tISOBAS8 */
+ YYSYMBOL_tISOBAS6 = 19, /* tISOBAS6 */
+ YYSYMBOL_tISOBASL = 20, /* tISOBASL */
+ YYSYMBOL_tDAY_UNIT = 21, /* tDAY_UNIT */
+ YYSYMBOL_tNEXT = 22, /* tNEXT */
+ YYSYMBOL_SP = 23, /* SP */
+ YYSYMBOL_24_ = 24, /* ':' */
+ YYSYMBOL_25_ = 25, /* ',' */
+ YYSYMBOL_26_ = 26, /* '-' */
+ YYSYMBOL_27_ = 27, /* '/' */
+ YYSYMBOL_28_T_ = 28, /* 'T' */
+ YYSYMBOL_29_ = 29, /* '.' */
+ YYSYMBOL_30_ = 30, /* '+' */
+ YYSYMBOL_YYACCEPT = 31, /* $accept */
+ YYSYMBOL_spec = 32, /* spec */
+ YYSYMBOL_item = 33, /* item */
+ YYSYMBOL_iextime = 34, /* iextime */
+ YYSYMBOL_time = 35, /* time */
+ YYSYMBOL_zone = 36, /* zone */
+ YYSYMBOL_comma = 37, /* comma */
+ YYSYMBOL_day = 38, /* day */
+ YYSYMBOL_iexdate = 39, /* iexdate */
+ YYSYMBOL_date = 40, /* date */
+ YYSYMBOL_ordMonth = 41, /* ordMonth */
+ YYSYMBOL_isosep = 42, /* isosep */
+ YYSYMBOL_isodate = 43, /* isodate */
+ YYSYMBOL_isotime = 44, /* isotime */
+ YYSYMBOL_iso = 45, /* iso */
+ YYSYMBOL_trek = 46, /* trek */
+ YYSYMBOL_relspec = 47, /* relspec */
+ YYSYMBOL_relunits = 48, /* relunits */
+ YYSYMBOL_sign = 49, /* sign */
+ YYSYMBOL_unit = 50, /* unit */
+ YYSYMBOL_INTNUM = 51, /* INTNUM */
+ YYSYMBOL_numitem = 52, /* numitem */
+ YYSYMBOL_o_merid = 53 /* o_merid */
};
typedef enum yysymbol_kind_t yysymbol_kind_t;
@@ -365,12 +322,10 @@ typedef enum yysymbol_kind_t yysymbol_kind_t;
*/
static int LookupWord(YYSTYPE* yylvalPtr, char *buff);
- static void TclDateerror(YYLTYPE* location,
+static void TclDateerror(YYLTYPE* location,
DateInfo* info, const char *s);
- static int TclDatelex(YYSTYPE* yylvalPtr, YYLTYPE* location,
+static int TclDatelex(YYSTYPE* yylvalPtr, YYLTYPE* location,
DateInfo* info);
-static time_t ToSeconds(time_t Hours, time_t Minutes,
- time_t Seconds, MERIDIAN Meridian);
MODULE_SCOPE int yyparse(DateInfo*);
@@ -700,19 +655,19 @@ union yyalloc
/* YYFINAL -- State number of the termination state. */
#define YYFINAL 2
/* YYLAST -- Last index in YYTABLE. */
-#define YYLAST 81
+#define YYLAST 98
/* YYNTOKENS -- Number of terminals. */
-#define YYNTOKENS 26
+#define YYNTOKENS 31
/* YYNNTS -- Number of nonterminals. */
-#define YYNNTS 16
+#define YYNNTS 23
/* YYNRULES -- Number of rules. */
-#define YYNRULES 56
+#define YYNRULES 72
/* YYNSTATES -- Number of states. */
-#define YYNSTATES 85
+#define YYNSTATES 103
/* YYMAXUTOK -- Last valid token kind. */
-#define YYMAXUTOK 274
+#define YYMAXUTOK 278
/* YYTRANSLATE(TOKEN-NUM) -- Symbol number corresponding to TOKEN-NUM
@@ -730,11 +685,11 @@ static const yytype_int8 yytranslate[] =
2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
- 2, 2, 2, 25, 21, 23, 24, 22, 2, 2,
- 2, 2, 2, 2, 2, 2, 2, 2, 20, 2,
- 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 30, 25, 26, 29, 27, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 24, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 28, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
@@ -753,19 +708,21 @@ static const yytype_int8 yytranslate[] =
2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 1, 2, 3, 4,
5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
- 15, 16, 17, 18, 19
+ 15, 16, 17, 18, 19, 20, 21, 22, 23
};
#if YYDEBUG
/* YYRLINE[YYN] -- Source line where rule number YYN was defined. */
static const yytype_int16 yyrline[] =
{
- 0, 223, 223, 224, 227, 230, 233, 236, 239, 242,
- 245, 249, 254, 257, 263, 269, 277, 282, 287, 291,
- 297, 301, 305, 309, 313, 319, 323, 328, 333, 338,
- 343, 347, 352, 356, 361, 368, 372, 378, 388, 397,
- 406, 416, 430, 435, 438, 441, 444, 447, 450, 455,
- 458, 463, 467, 471, 477, 495, 498
+ 0, 171, 171, 172, 176, 179, 182, 185, 188, 191,
+ 194, 197, 201, 204, 209, 215, 221, 226, 230, 234,
+ 238, 242, 246, 252, 253, 256, 260, 264, 268, 272,
+ 276, 282, 288, 292, 297, 298, 303, 307, 312, 316,
+ 321, 328, 332, 338, 338, 340, 345, 350, 352, 357,
+ 359, 360, 368, 379, 393, 398, 401, 404, 407, 410,
+ 413, 416, 421, 424, 429, 433, 437, 443, 446, 449,
+ 454, 472, 475
};
#endif
@@ -783,11 +740,12 @@ static const char *const yytname[] =
{
"\"end of file\"", "error", "\"invalid token\"", "tAGO", "tDAY",
"tDAYZONE", "tID", "tMERIDIAN", "tMONTH", "tMONTH_UNIT", "tSTARDATE",
- "tSEC_UNIT", "tSNUMBER", "tUNUMBER", "tZONE", "tEPOCH", "tDST",
- "tISOBASE", "tDAY_UNIT", "tNEXT", "':'", "','", "'/'", "'-'", "'.'",
- "'+'", "$accept", "spec", "item", "time", "zone", "day", "date",
- "ordMonth", "iso", "trek", "relspec", "relunits", "sign", "unit",
- "number", "o_merid", YY_NULLPTR
+ "tSEC_UNIT", "tUNUMBER", "tZONE", "tZONEwO4", "tZONEwO2", "tEPOCH",
+ "tDST", "tISOBAS8", "tISOBAS6", "tISOBASL", "tDAY_UNIT", "tNEXT", "SP",
+ "':'", "','", "'-'", "'/'", "'T'", "'.'", "'+'", "$accept", "spec",
+ "item", "iextime", "time", "zone", "comma", "day", "iexdate", "date",
+ "ordMonth", "isosep", "isodate", "isotime", "iso", "trek", "relspec",
+ "relunits", "sign", "unit", "INTNUM", "numitem", "o_merid", YY_NULLPTR
};
static const char *
@@ -797,12 +755,12 @@ yysymbol_name (yysymbol_kind_t yysymbol)
}
#endif
-#define YYPACT_NINF (-18)
+#define YYPACT_NINF (-21)
#define yypact_value_is_default(Yyn) \
((Yyn) == YYPACT_NINF)
-#define YYTABLE_NINF (-1)
+#define YYTABLE_NINF (-68)
#define yytable_value_is_error(Yyn) \
0
@@ -811,15 +769,17 @@ yysymbol_name (yysymbol_kind_t yysymbol)
STATE-NUM. */
static const yytype_int8 yypact[] =
{
- -18, 2, -18, -17, -18, -4, -18, 10, -18, 22,
- 8, -18, 18, -18, 39, -18, -18, -18, -18, -18,
- -18, -18, -18, -18, -18, -18, 25, 21, -18, -18,
- -18, 16, 14, -18, -18, 28, 36, 41, -5, -18,
- -18, 5, -18, -18, -18, 47, -18, -18, 42, 46,
- 48, -18, -6, 40, 43, 44, 49, -18, -18, -18,
- -18, -18, -18, -18, -18, 50, -18, 51, 55, 57,
- 58, 65, -18, -18, 59, 54, -18, 62, 63, 60,
- -18, 64, 61, 66, -18
+ -21, 11, -21, -20, -21, 5, -21, -9, -21, 46,
+ 17, 9, 9, -21, -21, -21, 24, -21, 57, -21,
+ -21, -21, 33, -21, -21, -21, -21, -21, -21, -15,
+ -21, -21, -21, 45, 26, -21, -7, -21, 51, -21,
+ -20, -21, -21, -21, 48, -21, -21, 67, 68, 52,
+ 69, -21, -9, -9, -21, -21, -21, -21, 74, -21,
+ -7, -21, -21, -21, -21, 44, -21, 79, 40, -7,
+ -21, -21, 72, 73, -21, 62, 61, 63, 64, -21,
+ -21, -21, -21, 66, -21, -21, -21, -21, 84, -7,
+ -21, -21, -21, 80, 81, 82, 83, -21, -21, -21,
+ -21, -21, -21
};
/* YYDEFACT[STATE-NUM] -- Default reduction number in state STATE-NUM.
@@ -827,29 +787,33 @@ static const yytype_int8 yypact[] =
means the default is an error. */
static const yytype_int8 yydefact[] =
{
- 2, 0, 1, 20, 18, 0, 53, 0, 51, 54,
- 17, 33, 27, 52, 0, 49, 50, 3, 4, 5,
- 8, 6, 7, 10, 11, 9, 43, 0, 48, 12,
- 21, 30, 0, 22, 13, 32, 0, 0, 0, 45,
- 16, 0, 40, 24, 35, 0, 46, 42, 19, 0,
- 0, 34, 55, 25, 0, 0, 0, 38, 36, 47,
- 23, 44, 31, 41, 56, 0, 14, 0, 0, 0,
- 0, 55, 26, 28, 29, 0, 15, 0, 0, 0,
- 39, 0, 0, 0, 37
+ 2, 0, 1, 25, 19, 0, 66, 0, 64, 70,
+ 18, 0, 0, 39, 45, 46, 0, 65, 0, 62,
+ 63, 3, 71, 4, 5, 8, 47, 6, 7, 34,
+ 10, 11, 9, 55, 0, 61, 0, 12, 23, 26,
+ 36, 67, 69, 68, 0, 27, 15, 38, 0, 0,
+ 0, 17, 0, 0, 52, 51, 30, 41, 67, 59,
+ 0, 72, 16, 44, 43, 0, 54, 67, 0, 22,
+ 58, 24, 0, 0, 40, 14, 0, 0, 32, 20,
+ 21, 42, 60, 0, 48, 49, 50, 29, 67, 0,
+ 57, 37, 53, 0, 0, 0, 0, 28, 56, 13,
+ 35, 31, 33
};
/* YYPGOTO[NTERM-NUM]. */
static const yytype_int8 yypgoto[] =
{
- -18, -18, -18, -18, -18, -18, -18, -18, -18, -18,
- -18, -18, -18, -9, -18, 7
+ -21, -21, -21, 31, -21, -21, 58, -21, -21, -21,
+ -21, -21, -21, -21, -21, -21, -21, -21, -5, -18,
+ -6, -21, -21
};
/* YYDEFGOTO[NTERM-NUM]. */
static const yytype_int8 yydefgoto[] =
{
- 0, 1, 17, 18, 19, 20, 21, 22, 23, 24,
- 25, 26, 27, 28, 29, 66
+ 0, 1, 21, 22, 23, 24, 39, 25, 26, 27,
+ 28, 65, 29, 86, 30, 31, 32, 33, 34, 35,
+ 36, 37, 62
};
/* YYTABLE[YYPACT[STATE-NUM]] -- What to do in state STATE-NUM. If
@@ -857,65 +821,73 @@ static const yytype_int8 yydefgoto[] =
number is the opposite. If YYTABLE_NINF, syntax error. */
static const yytype_int8 yytable[] =
{
- 39, 64, 2, 54, 30, 46, 3, 4, 55, 31,
- 5, 6, 7, 8, 65, 9, 10, 11, 56, 12,
- 13, 14, 57, 32, 40, 15, 33, 16, 47, 34,
- 35, 6, 41, 8, 48, 42, 59, 49, 50, 61,
- 13, 51, 36, 43, 37, 38, 60, 44, 6, 52,
- 8, 6, 45, 8, 53, 58, 6, 13, 8, 62,
- 13, 63, 67, 71, 72, 13, 68, 69, 73, 70,
- 74, 75, 64, 77, 78, 79, 80, 82, 76, 84,
- 81, 83
+ 59, 44, 6, 41, 8, 38, 52, 53, 63, 42,
+ 43, 2, 60, 64, 17, 3, 4, 40, 70, 5,
+ 6, 7, 8, 9, 10, 11, 12, 13, 69, 14,
+ 15, 16, 17, 18, 51, 19, 54, 19, 67, 20,
+ 61, 20, 82, 55, 42, 43, 79, 80, 66, 68,
+ 45, 90, 88, 46, 47, -67, 83, -67, 42, 43,
+ 76, 56, 89, 84, 77, 57, 6, -67, 8, 58,
+ 48, 98, 49, 50, 71, 42, 43, 73, 17, 74,
+ 75, 78, 81, 87, 91, 92, 93, 94, 97, 95,
+ 48, 96, 99, 100, 101, 102, 85, 0, 72
};
static const yytype_int8 yycheck[] =
{
- 9, 7, 0, 8, 21, 14, 4, 5, 13, 13,
- 8, 9, 10, 11, 20, 13, 14, 15, 13, 17,
- 18, 19, 17, 13, 16, 23, 4, 25, 3, 7,
- 8, 9, 14, 11, 13, 17, 45, 21, 24, 48,
- 18, 13, 20, 4, 22, 23, 4, 8, 9, 13,
- 11, 9, 13, 11, 13, 8, 9, 18, 11, 13,
- 18, 13, 22, 13, 13, 18, 23, 23, 13, 20,
- 13, 13, 7, 14, 20, 13, 13, 13, 71, 13,
- 20, 20
+ 18, 7, 9, 12, 11, 25, 11, 12, 23, 18,
+ 19, 0, 18, 28, 21, 4, 5, 12, 36, 8,
+ 9, 10, 11, 12, 13, 14, 15, 16, 34, 18,
+ 19, 20, 21, 22, 17, 26, 12, 26, 12, 30,
+ 7, 30, 60, 19, 18, 19, 52, 53, 3, 23,
+ 4, 69, 12, 7, 8, 9, 12, 11, 18, 19,
+ 8, 4, 68, 19, 12, 8, 9, 21, 11, 12,
+ 24, 89, 26, 27, 23, 18, 19, 29, 21, 12,
+ 12, 12, 8, 4, 12, 12, 24, 26, 4, 26,
+ 24, 27, 12, 12, 12, 12, 65, -1, 40
};
/* YYSTOS[STATE-NUM] -- The symbol kind of the accessing symbol of
state STATE-NUM. */
static const yytype_int8 yystos[] =
{
- 0, 27, 0, 4, 5, 8, 9, 10, 11, 13,
- 14, 15, 17, 18, 19, 23, 25, 28, 29, 30,
- 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
- 21, 13, 13, 4, 7, 8, 20, 22, 23, 39,
- 16, 14, 17, 4, 8, 13, 39, 3, 13, 21,
- 24, 13, 13, 13, 8, 13, 13, 17, 8, 39,
- 4, 39, 13, 13, 7, 20, 41, 22, 23, 23,
- 20, 13, 13, 13, 13, 13, 41, 14, 20, 13,
- 13, 20, 13, 20, 13
+ 0, 32, 0, 4, 5, 8, 9, 10, 11, 12,
+ 13, 14, 15, 16, 18, 19, 20, 21, 22, 26,
+ 30, 33, 34, 35, 36, 38, 39, 40, 41, 43,
+ 45, 46, 47, 48, 49, 50, 51, 52, 25, 37,
+ 12, 12, 18, 19, 51, 4, 7, 8, 24, 26,
+ 27, 17, 49, 49, 12, 19, 4, 8, 12, 50,
+ 51, 7, 53, 23, 28, 42, 3, 12, 23, 51,
+ 50, 23, 37, 29, 12, 12, 8, 12, 12, 51,
+ 51, 8, 50, 12, 19, 34, 44, 4, 12, 51,
+ 50, 12, 12, 24, 26, 26, 27, 4, 50, 12,
+ 12, 12, 12
};
/* YYR1[RULE-NUM] -- Symbol kind of the left-hand side of rule RULE-NUM. */
static const yytype_int8 yyr1[] =
{
- 0, 26, 27, 27, 28, 28, 28, 28, 28, 28,
- 28, 28, 28, 29, 29, 29, 30, 30, 30, 30,
- 31, 31, 31, 31, 31, 32, 32, 32, 32, 32,
- 32, 32, 32, 32, 32, 33, 33, 34, 34, 34,
- 34, 35, 36, 36, 37, 37, 37, 37, 37, 38,
- 38, 39, 39, 39, 40, 41, 41
+ 0, 31, 32, 32, 33, 33, 33, 33, 33, 33,
+ 33, 33, 33, 34, 34, 35, 35, 36, 36, 36,
+ 36, 36, 36, 37, 37, 38, 38, 38, 38, 38,
+ 38, 39, 40, 40, 40, 40, 40, 40, 40, 40,
+ 40, 41, 41, 42, 42, 43, 43, 43, 44, 44,
+ 45, 45, 45, 46, 47, 47, 48, 48, 48, 48,
+ 48, 48, 49, 49, 50, 50, 50, 51, 51, 51,
+ 52, 53, 53
};
/* YYR2[RULE-NUM] -- Number of symbols on the right-hand side of rule RULE-NUM. */
static const yytype_int8 yyr2[] =
{
0, 2, 0, 2, 1, 1, 1, 1, 1, 1,
- 1, 1, 1, 2, 4, 6, 2, 1, 1, 2,
- 1, 2, 2, 3, 2, 3, 5, 1, 5, 5,
- 2, 4, 2, 1, 3, 2, 3, 11, 3, 7,
- 2, 4, 2, 1, 3, 2, 2, 3, 1, 1,
- 1, 1, 1, 1, 1, 0, 1
+ 1, 1, 1, 5, 3, 2, 2, 2, 1, 1,
+ 3, 3, 2, 1, 2, 1, 2, 2, 4, 3,
+ 2, 5, 3, 5, 1, 5, 2, 4, 2, 1,
+ 3, 2, 3, 1, 1, 1, 1, 1, 1, 1,
+ 3, 2, 2, 4, 2, 1, 4, 3, 2, 2,
+ 3, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 0, 1
};
@@ -1500,155 +1472,184 @@ yyreduce:
{
case 4: /* item: time */
{
- yyHaveTime++;
+ yyIncrFlags(CLF_TIME);
}
break;
case 5: /* item: zone */
{
- yyHaveZone++;
+ yyIncrFlags(CLF_ZONE);
}
break;
case 6: /* item: date */
{
- yyHaveDate++;
+ yyIncrFlags(CLF_HAVEDATE);
}
break;
case 7: /* item: ordMonth */
{
- yyHaveOrdinalMonth++;
+ yyIncrFlags(CLF_ORDINALMONTH);
}
break;
case 8: /* item: day */
{
- yyHaveDay++;
+ yyIncrFlags(CLF_DAYOFWEEK);
}
break;
case 9: /* item: relspec */
{
- yyHaveRel++;
+ info->flags |= CLF_RELCONV;
}
break;
case 10: /* item: iso */
{
- yyHaveTime++;
- yyHaveDate++;
+ yyIncrFlags(CLF_TIME|CLF_HAVEDATE);
}
break;
case 11: /* item: trek */
{
- yyHaveTime++;
- yyHaveDate++;
- yyHaveRel++;
+ yyIncrFlags(CLF_TIME|CLF_HAVEDATE);
+ info->flags |= CLF_RELCONV;
}
break;
- case 13: /* time: tUNUMBER tMERIDIAN */
- {
- yyHour = (yyvsp[-1].Number);
- yyMinutes = 0;
+ case 13: /* iextime: tUNUMBER ':' tUNUMBER ':' tUNUMBER */
+ {
+ yyHour = (yyvsp[-4].Number);
+ yyMinutes = (yyvsp[-2].Number);
+ yySeconds = (yyvsp[0].Number);
+ }
+ break;
+
+ case 14: /* iextime: tUNUMBER ':' tUNUMBER */
+ {
+ yyHour = (yyvsp[-2].Number);
+ yyMinutes = (yyvsp[0].Number);
yySeconds = 0;
- yyMeridian = (yyvsp[0].Meridian);
}
break;
- case 14: /* time: tUNUMBER ':' tUNUMBER o_merid */
- {
- yyHour = (yyvsp[-3].Number);
- yyMinutes = (yyvsp[-1].Number);
+ case 15: /* time: tUNUMBER tMERIDIAN */
+ {
+ yyHour = (yyvsp[-1].Number);
+ yyMinutes = 0;
yySeconds = 0;
yyMeridian = (yyvsp[0].Meridian);
}
break;
- case 15: /* time: tUNUMBER ':' tUNUMBER ':' tUNUMBER o_merid */
- {
- yyHour = (yyvsp[-5].Number);
- yyMinutes = (yyvsp[-3].Number);
- yySeconds = (yyvsp[-1].Number);
+ case 16: /* time: iextime o_merid */
+ {
yyMeridian = (yyvsp[0].Meridian);
}
break;
- case 16: /* zone: tZONE tDST */
+ case 17: /* zone: tZONE tDST */
{
yyTimezone = (yyvsp[-1].Number);
- if (yyTimezone > HOUR( 12)) yyTimezone -= HOUR(100);
yyDSTmode = DSTon;
}
break;
- case 17: /* zone: tZONE */
+ case 18: /* zone: tZONE */
{
yyTimezone = (yyvsp[0].Number);
- if (yyTimezone > HOUR( 12)) yyTimezone -= HOUR(100);
yyDSTmode = DSToff;
}
break;
- case 18: /* zone: tDAYZONE */
+ case 19: /* zone: tDAYZONE */
{
yyTimezone = (yyvsp[0].Number);
yyDSTmode = DSTon;
}
break;
- case 19: /* zone: sign tUNUMBER */
- {
+ case 20: /* zone: tZONEwO4 sign INTNUM */
+ { /* GMT+0100, GMT-1000, etc. */
+ yyTimezone = (yyvsp[-2].Number) - (yyvsp[-1].Number)*((yyvsp[0].Number) % 100 + ((yyvsp[0].Number) / 100) * 60);
+ yyDSTmode = DSToff;
+ }
+ break;
+
+ case 21: /* zone: tZONEwO2 sign INTNUM */
+ { /* GMT+1, GMT-10, etc. */
+ yyTimezone = (yyvsp[-2].Number) - (yyvsp[-1].Number)*((yyvsp[0].Number) * 60);
+ yyDSTmode = DSToff;
+ }
+ break;
+
+ case 22: /* zone: sign INTNUM */
+ { /* +0100, -0100 */
yyTimezone = -(yyvsp[-1].Number)*((yyvsp[0].Number) % 100 + ((yyvsp[0].Number) / 100) * 60);
yyDSTmode = DSToff;
}
break;
- case 20: /* day: tDAY */
+ case 25: /* day: tDAY */
{
yyDayOrdinal = 1;
- yyDayNumber = (yyvsp[0].Number);
+ yyDayOfWeek = (yyvsp[0].Number);
}
break;
- case 21: /* day: tDAY ',' */
- {
+ case 26: /* day: tDAY comma */
+ {
yyDayOrdinal = 1;
- yyDayNumber = (yyvsp[-1].Number);
+ yyDayOfWeek = (yyvsp[-1].Number);
}
break;
- case 22: /* day: tUNUMBER tDAY */
+ case 27: /* day: tUNUMBER tDAY */
{
yyDayOrdinal = (yyvsp[-1].Number);
- yyDayNumber = (yyvsp[0].Number);
+ yyDayOfWeek = (yyvsp[0].Number);
}
break;
- case 23: /* day: sign tUNUMBER tDAY */
+ case 28: /* day: sign SP tUNUMBER tDAY */
+ {
+ yyDayOrdinal = (yyvsp[-3].Number) * (yyvsp[-1].Number);
+ yyDayOfWeek = (yyvsp[0].Number);
+ }
+ break;
+
+ case 29: /* day: sign tUNUMBER tDAY */
{
yyDayOrdinal = (yyvsp[-2].Number) * (yyvsp[-1].Number);
- yyDayNumber = (yyvsp[0].Number);
+ yyDayOfWeek = (yyvsp[0].Number);
}
break;
- case 24: /* day: tNEXT tDAY */
+ case 30: /* day: tNEXT tDAY */
{
yyDayOrdinal = 2;
- yyDayNumber = (yyvsp[0].Number);
+ yyDayOfWeek = (yyvsp[0].Number);
+ }
+ break;
+
+ case 31: /* iexdate: tUNUMBER '-' tUNUMBER '-' tUNUMBER */
+ {
+ yyMonth = (yyvsp[-2].Number);
+ yyDay = (yyvsp[0].Number);
+ yyYear = (yyvsp[-4].Number);
}
break;
- case 25: /* date: tUNUMBER '/' tUNUMBER */
+ case 32: /* date: tUNUMBER '/' tUNUMBER */
{
yyMonth = (yyvsp[-2].Number);
yyDay = (yyvsp[0].Number);
}
break;
- case 26: /* date: tUNUMBER '/' tUNUMBER '/' tUNUMBER */
+ case 33: /* date: tUNUMBER '/' tUNUMBER '/' tUNUMBER */
{
yyMonth = (yyvsp[-4].Number);
yyDay = (yyvsp[-2].Number);
@@ -1656,15 +1657,7 @@ yyreduce:
}
break;
- case 27: /* date: tISOBASE */
- {
- yyYear = (yyvsp[0].Number) / 10000;
- yyMonth = ((yyvsp[0].Number) % 10000)/100;
- yyDay = (yyvsp[0].Number) % 100;
- }
- break;
-
- case 28: /* date: tUNUMBER '-' tMONTH '-' tUNUMBER */
+ case 35: /* date: tUNUMBER '-' tMONTH '-' tUNUMBER */
{
yyDay = (yyvsp[-4].Number);
yyMonth = (yyvsp[-2].Number);
@@ -1672,37 +1665,29 @@ yyreduce:
}
break;
- case 29: /* date: tUNUMBER '-' tUNUMBER '-' tUNUMBER */
- {
- yyMonth = (yyvsp[-2].Number);
- yyDay = (yyvsp[0].Number);
- yyYear = (yyvsp[-4].Number);
- }
- break;
-
- case 30: /* date: tMONTH tUNUMBER */
+ case 36: /* date: tMONTH tUNUMBER */
{
yyMonth = (yyvsp[-1].Number);
yyDay = (yyvsp[0].Number);
}
break;
- case 31: /* date: tMONTH tUNUMBER ',' tUNUMBER */
- {
+ case 37: /* date: tMONTH tUNUMBER comma tUNUMBER */
+ {
yyMonth = (yyvsp[-3].Number);
yyDay = (yyvsp[-2].Number);
yyYear = (yyvsp[0].Number);
}
break;
- case 32: /* date: tUNUMBER tMONTH */
+ case 38: /* date: tUNUMBER tMONTH */
{
yyMonth = (yyvsp[0].Number);
yyDay = (yyvsp[-1].Number);
}
break;
- case 33: /* date: tEPOCH */
+ case 39: /* date: tEPOCH */
{
yyMonth = 1;
yyDay = 1;
@@ -1710,7 +1695,7 @@ yyreduce:
}
break;
- case 34: /* date: tUNUMBER tMONTH tUNUMBER */
+ case 40: /* date: tUNUMBER tMONTH tUNUMBER */
{
yyMonth = (yyvsp[-1].Number);
yyDay = (yyvsp[-2].Number);
@@ -1718,58 +1703,46 @@ yyreduce:
}
break;
- case 35: /* ordMonth: tNEXT tMONTH */
+ case 41: /* ordMonth: tNEXT tMONTH */
{
- yyMonthOrdinal = 1;
- yyMonth = (yyvsp[0].Number);
+ yyMonthOrdinalIncr = 1;
+ yyMonthOrdinal = (yyvsp[0].Number);
}
break;
- case 36: /* ordMonth: tNEXT tUNUMBER tMONTH */
+ case 42: /* ordMonth: tNEXT tUNUMBER tMONTH */
{
- yyMonthOrdinal = (yyvsp[-1].Number);
- yyMonth = (yyvsp[0].Number);
+ yyMonthOrdinalIncr = (yyvsp[-1].Number);
+ yyMonthOrdinal = (yyvsp[0].Number);
}
break;
- case 37: /* iso: tUNUMBER '-' tUNUMBER '-' tUNUMBER tZONE tUNUMBER ':' tUNUMBER ':' tUNUMBER */
- {
- if ((yyvsp[-5].Number) != HOUR( 7) + HOUR(100)) YYABORT;
- yyYear = (yyvsp[-10].Number);
- yyMonth = (yyvsp[-8].Number);
- yyDay = (yyvsp[-6].Number);
- yyHour = (yyvsp[-4].Number);
- yyMinutes = (yyvsp[-2].Number);
- yySeconds = (yyvsp[0].Number);
+ case 45: /* isodate: tISOBAS8 */
+ { /* YYYYMMDD */
+ yyYear = (yyvsp[0].Number) / 10000;
+ yyMonth = ((yyvsp[0].Number) % 10000)/100;
+ yyDay = (yyvsp[0].Number) % 100;
}
break;
- case 38: /* iso: tISOBASE tZONE tISOBASE */
- {
- if ((yyvsp[-1].Number) != HOUR( 7) + HOUR(100)) YYABORT;
- yyYear = (yyvsp[-2].Number) / 10000;
- yyMonth = ((yyvsp[-2].Number) % 10000)/100;
- yyDay = (yyvsp[-2].Number) % 100;
- yyHour = (yyvsp[0].Number) / 10000;
- yyMinutes = ((yyvsp[0].Number) % 10000)/100;
- yySeconds = (yyvsp[0].Number) % 100;
+ case 46: /* isodate: tISOBAS6 */
+ { /* YYMMDD */
+ yyYear = (yyvsp[0].Number) / 10000;
+ yyMonth = ((yyvsp[0].Number) % 10000)/100;
+ yyDay = (yyvsp[0].Number) % 100;
}
break;
- case 39: /* iso: tISOBASE tZONE tUNUMBER ':' tUNUMBER ':' tUNUMBER */
- {
- if ((yyvsp[-5].Number) != HOUR( 7) + HOUR(100)) YYABORT;
- yyYear = (yyvsp[-6].Number) / 10000;
- yyMonth = ((yyvsp[-6].Number) % 10000)/100;
- yyDay = (yyvsp[-6].Number) % 100;
- yyHour = (yyvsp[-4].Number);
- yyMinutes = (yyvsp[-2].Number);
- yySeconds = (yyvsp[0].Number);
+ case 48: /* isotime: tISOBAS6 */
+ {
+ yyHour = (yyvsp[0].Number) / 10000;
+ yyMinutes = ((yyvsp[0].Number) % 10000)/100;
+ yySeconds = (yyvsp[0].Number) % 100;
}
break;
- case 40: /* iso: tISOBASE tISOBASE */
- {
+ case 51: /* iso: tISOBASL tISOBAS6 */
+ { /* YYYYMMDDhhmmss */
yyYear = (yyvsp[-1].Number) / 10000;
yyMonth = ((yyvsp[-1].Number) % 10000)/100;
yyDay = (yyvsp[-1].Number) % 100;
@@ -1779,8 +1752,20 @@ yyreduce:
}
break;
- case 41: /* trek: tSTARDATE tUNUMBER '.' tUNUMBER */
- {
+ case 52: /* iso: tISOBASL tUNUMBER */
+ { /* YYYYMMDDhhmm */
+ if (yyDigitCount != 4) YYABORT; /* normally unreached */
+ yyYear = (yyvsp[-1].Number) / 10000;
+ yyMonth = ((yyvsp[-1].Number) % 10000)/100;
+ yyDay = (yyvsp[-1].Number) % 100;
+ yyHour = (yyvsp[0].Number) / 100;
+ yyMinutes = ((yyvsp[0].Number) % 100);
+ yySeconds = 0;
+ }
+ break;
+
+ case 53: /* trek: tSTARDATE INTNUM '.' tUNUMBER */
+ {
/*
* Offset computed year by -377 so that the returned years will be
* in a range accessible with a 32 bit clock seconds value.
@@ -1790,11 +1775,11 @@ yyreduce:
yyDay = 1;
yyMonth = 1;
yyRelDay += (((yyvsp[-2].Number)%1000)*(365 + IsLeapYear(yyYear)))/1000;
- yyRelSeconds += (yyvsp[0].Number) * 144 * 60;
+ yyRelSeconds += (yyvsp[0].Number) * (144LL * 60LL);
}
break;
- case 42: /* relspec: relunits tAGO */
+ case 54: /* relspec: relunits tAGO */
{
yyRelSeconds *= -1;
yyRelMonth *= -1;
@@ -1802,75 +1787,99 @@ yyreduce:
}
break;
- case 44: /* relunits: sign tUNUMBER unit */
- {
+ case 56: /* relunits: sign SP INTNUM unit */
+ {
+ *yyRelPointer += (yyvsp[-3].Number) * (yyvsp[-1].Number) * (yyvsp[0].Number);
+ }
+ break;
+
+ case 57: /* relunits: sign INTNUM unit */
+ {
*yyRelPointer += (yyvsp[-2].Number) * (yyvsp[-1].Number) * (yyvsp[0].Number);
}
break;
- case 45: /* relunits: tUNUMBER unit */
- {
+ case 58: /* relunits: INTNUM unit */
+ {
*yyRelPointer += (yyvsp[-1].Number) * (yyvsp[0].Number);
}
break;
- case 46: /* relunits: tNEXT unit */
+ case 59: /* relunits: tNEXT unit */
{
*yyRelPointer += (yyvsp[0].Number);
}
break;
- case 47: /* relunits: tNEXT tUNUMBER unit */
- {
+ case 60: /* relunits: tNEXT INTNUM unit */
+ {
*yyRelPointer += (yyvsp[-1].Number) * (yyvsp[0].Number);
}
break;
- case 48: /* relunits: unit */
+ case 61: /* relunits: unit */
{
*yyRelPointer += (yyvsp[0].Number);
}
break;
- case 49: /* sign: '-' */
+ case 62: /* sign: '-' */
{
(yyval.Number) = -1;
}
break;
- case 50: /* sign: '+' */
+ case 63: /* sign: '+' */
{
(yyval.Number) = 1;
}
break;
- case 51: /* unit: tSEC_UNIT */
+ case 64: /* unit: tSEC_UNIT */
{
(yyval.Number) = (yyvsp[0].Number);
yyRelPointer = &yyRelSeconds;
}
break;
- case 52: /* unit: tDAY_UNIT */
+ case 65: /* unit: tDAY_UNIT */
{
(yyval.Number) = (yyvsp[0].Number);
yyRelPointer = &yyRelDay;
}
break;
- case 53: /* unit: tMONTH_UNIT */
+ case 66: /* unit: tMONTH_UNIT */
{
(yyval.Number) = (yyvsp[0].Number);
yyRelPointer = &yyRelMonth;
}
break;
- case 54: /* number: tUNUMBER */
+ case 67: /* INTNUM: tUNUMBER */
{
- if (yyHaveTime && yyHaveDate && !yyHaveRel) {
+ (yyval.Number) = (yyvsp[0].Number);
+ }
+ break;
+
+ case 68: /* INTNUM: tISOBAS6 */
+ {
+ (yyval.Number) = (yyvsp[0].Number);
+ }
+ break;
+
+ case 69: /* INTNUM: tISOBAS8 */
+ {
+ (yyval.Number) = (yyvsp[0].Number);
+ }
+ break;
+
+ case 70: /* numitem: tUNUMBER */
+ {
+ if ((info->flags & (CLF_TIME|CLF_HAVEDATE|CLF_RELCONV)) == (CLF_TIME|CLF_HAVEDATE)) {
yyYear = (yyvsp[0].Number);
} else {
- yyHaveTime++;
+ yyIncrFlags(CLF_TIME);
if (yyDigitCount <= 2) {
yyHour = (yyvsp[0].Number);
yyMinutes = 0;
@@ -1884,13 +1893,13 @@ yyreduce:
}
break;
- case 55: /* o_merid: %empty */
+ case 71: /* o_merid: %empty */
{
(yyval.Meridian) = MER24;
}
break;
- case 56: /* o_merid: tMERIDIAN */
+ case 72: /* o_merid: tMERIDIAN */
{
(yyval.Meridian) = (yyvsp[0].Meridian);
}
@@ -2155,20 +2164,6 @@ static const TABLE OtherTable[] = {
{ "last", tUNUMBER, -1 },
{ "this", tSEC_UNIT, 0 },
{ "next", tNEXT, 1 },
-#if 0
- { "first", tUNUMBER, 1 },
- { "second", tUNUMBER, 2 },
- { "third", tUNUMBER, 3 },
- { "fourth", tUNUMBER, 4 },
- { "fifth", tUNUMBER, 5 },
- { "sixth", tUNUMBER, 6 },
- { "seventh", tUNUMBER, 7 },
- { "eighth", tUNUMBER, 8 },
- { "ninth", tUNUMBER, 9 },
- { "tenth", tUNUMBER, 10 },
- { "eleventh", tUNUMBER, 11 },
- { "twelfth", tUNUMBER, 12 },
-#endif
{ "ago", tAGO, 1 },
{ "epoch", tEPOCH, 0 },
{ "stardate", tSTARDATE, 0 },
@@ -2268,34 +2263,44 @@ static const TABLE TimezoneTable[] = {
*/
static const TABLE MilitaryTable[] = {
- { "a", tZONE, -HOUR( 1) + HOUR(100) },
- { "b", tZONE, -HOUR( 2) + HOUR(100) },
- { "c", tZONE, -HOUR( 3) + HOUR(100) },
- { "d", tZONE, -HOUR( 4) + HOUR(100) },
- { "e", tZONE, -HOUR( 5) + HOUR(100) },
- { "f", tZONE, -HOUR( 6) + HOUR(100) },
- { "g", tZONE, -HOUR( 7) + HOUR(100) },
- { "h", tZONE, -HOUR( 8) + HOUR(100) },
- { "i", tZONE, -HOUR( 9) + HOUR(100) },
- { "k", tZONE, -HOUR(10) + HOUR(100) },
- { "l", tZONE, -HOUR(11) + HOUR(100) },
- { "m", tZONE, -HOUR(12) + HOUR(100) },
- { "n", tZONE, HOUR( 1) + HOUR(100) },
- { "o", tZONE, HOUR( 2) + HOUR(100) },
- { "p", tZONE, HOUR( 3) + HOUR(100) },
- { "q", tZONE, HOUR( 4) + HOUR(100) },
- { "r", tZONE, HOUR( 5) + HOUR(100) },
- { "s", tZONE, HOUR( 6) + HOUR(100) },
- { "t", tZONE, HOUR( 7) + HOUR(100) },
- { "u", tZONE, HOUR( 8) + HOUR(100) },
- { "v", tZONE, HOUR( 9) + HOUR(100) },
- { "w", tZONE, HOUR( 10) + HOUR(100) },
- { "x", tZONE, HOUR( 11) + HOUR(100) },
- { "y", tZONE, HOUR( 12) + HOUR(100) },
- { "z", tZONE, HOUR( 0) + HOUR(100) },
+ { "a", tZONE, -HOUR( 1) },
+ { "b", tZONE, -HOUR( 2) },
+ { "c", tZONE, -HOUR( 3) },
+ { "d", tZONE, -HOUR( 4) },
+ { "e", tZONE, -HOUR( 5) },
+ { "f", tZONE, -HOUR( 6) },
+ { "g", tZONE, -HOUR( 7) },
+ { "h", tZONE, -HOUR( 8) },
+ { "i", tZONE, -HOUR( 9) },
+ { "k", tZONE, -HOUR(10) },
+ { "l", tZONE, -HOUR(11) },
+ { "m", tZONE, -HOUR(12) },
+ { "n", tZONE, HOUR( 1) },
+ { "o", tZONE, HOUR( 2) },
+ { "p", tZONE, HOUR( 3) },
+ { "q", tZONE, HOUR( 4) },
+ { "r", tZONE, HOUR( 5) },
+ { "s", tZONE, HOUR( 6) },
+ { "t", tZONE, HOUR( 7) },
+ { "u", tZONE, HOUR( 8) },
+ { "v", tZONE, HOUR( 9) },
+ { "w", tZONE, HOUR( 10) },
+ { "x", tZONE, HOUR( 11) },
+ { "y", tZONE, HOUR( 12) },
+ { "z", tZONE, HOUR( 0) },
{ NULL, 0, 0 }
};
+static inline const char *
+bypassSpaces(
+ const char *s)
+{
+ while (TclIsSpaceProc(*s)) {
+ s++;
+ }
+ return s;
+}
+
/*
* Dump error messages in the bit bucket.
*/
@@ -2307,6 +2312,9 @@ TclDateerror(
const char *s)
{
Tcl_Obj* t;
+ if (!infoPtr->messages) {
+ TclNewObj(infoPtr->messages);
+ }
Tcl_AppendToObj(infoPtr->messages, infoPtr->separatrix, -1);
Tcl_AppendToObj(infoPtr->messages, s, -1);
Tcl_AppendToObj(infoPtr->messages, " (characters ", -1);
@@ -2323,11 +2331,11 @@ TclDateerror(
infoPtr->separatrix = "\n";
}
-static time_t
+int
ToSeconds(
- time_t Hours,
- time_t Minutes,
- time_t Seconds,
+ int Hours,
+ int Minutes,
+ int Seconds,
MERIDIAN Meridian)
{
if (Minutes < 0 || Minutes > 59 || Seconds < 0 || Seconds > 59) {
@@ -2338,17 +2346,17 @@ ToSeconds(
if (Hours < 0 || Hours > 23) {
return -1;
}
- return (Hours * 60L + Minutes) * 60L + Seconds;
+ return (Hours * 60 + Minutes) * 60 + Seconds;
case MERam:
if (Hours < 1 || Hours > 12) {
return -1;
}
- return ((Hours % 12) * 60L + Minutes) * 60L + Seconds;
+ return ((Hours % 12) * 60 + Minutes) * 60 + Seconds;
case MERpm:
if (Hours < 1 || Hours > 12) {
return -1;
}
- return (((Hours % 12) + 12) * 60L + Minutes) * 60L + Seconds;
+ return (((Hours % 12) + 12) * 60 + Minutes) * 60 + Seconds;
}
return -1; /* Should never be reached */
}
@@ -2369,11 +2377,11 @@ LookupWord(
Tcl_UtfToLower(buff);
- if (strcmp(buff, "am") == 0 || strcmp(buff, "a.m.") == 0) {
+ if (*buff == 'a' && (strcmp(buff, "am") == 0 || strcmp(buff, "a.m.") == 0)) {
yylvalPtr->Meridian = MERam;
return tMERIDIAN;
}
- if (strcmp(buff, "pm") == 0 || strcmp(buff, "p.m.") == 0) {
+ if (*buff == 'p' && (strcmp(buff, "pm") == 0 || strcmp(buff, "p.m.") == 0)) {
yylvalPtr->Meridian = MERpm;
return tMERIDIAN;
}
@@ -2487,50 +2495,114 @@ TclDatelex(
char *p;
char buff[20];
int Count;
+ const char *tokStart;
location->first_column = yyInput - info->dateStart;
for ( ; ; ) {
- while (TclIsSpaceProcM(*yyInput)) {
- yyInput++;
+
+ if (isspace(UCHAR(*yyInput))) {
+ yyInput = bypassSpaces(yyInput);
+ /* ignore space at end of text and before some words */
+ c = *yyInput;
+ if (c != '\0' && !isalpha(UCHAR(c))) {
+ return SP;
+ }
}
+ tokStart = yyInput;
if (isdigit(UCHAR(c = *yyInput))) { /* INTL: digit */
+
/*
* Convert the string into a number; count the number of digits.
*/
-
- Count = 0;
- for (yylvalPtr->Number = 0;
- isdigit(UCHAR(c = *yyInput++)); ) { /* INTL: digit */
- yylvalPtr->Number = 10 * yylvalPtr->Number + c - '0';
- Count++;
+ long long num = c - '0';
+ p = (char *)yyInput;
+ while (isdigit(UCHAR(c = *(++p)))) {
+ if (num >= 0) {
+ num *= 10; num += c - '0';
+ }
}
- yyInput--;
- yyDigitCount = Count;
+ yylvalPtr->Number = num;
+ yyDigitCount = p - yyInput;
+ yyInput = p;
/*
* A number with 6 or more digits is considered an ISO 8601 base.
*/
- if (Count >= 6) {
- location->last_column = yyInput - info->dateStart - 1;
- return tISOBASE;
- } else {
- location->last_column = yyInput - info->dateStart - 1;
- return tUNUMBER;
+ location->last_column = yyInput - info->dateStart - 1;
+ if (yyDigitCount >= 6) {
+ if (yyDigitCount == 14 || yyDigitCount == 12) {
+ /* long form of ISO 8601 (without separator), either
+ * YYYYMMDDhhmmss or YYYYMMDDhhmm, so reduce to date
+ * (8 chars is isodate) */
+ p = (char *)tokStart;
+ num = *p++ - '0';
+ do {
+ num *= 10; num += *p++ - '0';
+ } while (p - tokStart < 8);
+ yylvalPtr->Number = num;
+ yyDigitCount = 8;
+ yyInput = p;
+ location->last_column = yyInput - info->dateStart - 1;
+ return tISOBASL;
+ }
+ if (yyDigitCount > 14) { /* overflow */
+ return tID;
+ }
+ if (yyDigitCount == 8) {
+ return tISOBAS8;
+ }
+ if (yyDigitCount == 6) {
+ return tISOBAS6;
+ }
}
+ /* ignore spaces after digits (optional) */
+ yyInput = bypassSpaces(yyInput);
+ return tUNUMBER;
}
if (!(c & 0x80) && isalpha(UCHAR(c))) { /* INTL: ISO only. */
+ int ret;
for (p = buff; isalpha(UCHAR(c = *yyInput++)) /* INTL: ISO only. */
|| c == '.'; ) {
- if (p < &buff[sizeof buff - 1]) {
+ if (p < &buff[sizeof(buff) - 1]) {
*p++ = c;
}
}
*p = '\0';
yyInput--;
location->last_column = yyInput - info->dateStart - 1;
- return LookupWord(yylvalPtr, buff);
+ ret = LookupWord(yylvalPtr, buff);
+ /*
+ * lookahead:
+ * for spaces to consider word boundaries (for instance
+ * literal T in isodateTisotimeZ is not a TZ, but Z is UTC);
+ * for +/- digit, to differentiate between "GMT+1000 day" and "GMT +1000 day";
+ * bypass spaces after token (but ignore by TZ+OFFS), because should
+ * recognize next SP token, if TZ only.
+ */
+ if (ret == tZONE || ret == tDAYZONE) {
+ c = *yyInput;
+ if (isdigit(UCHAR(c))) { /* literal not a TZ */
+ yyInput = tokStart;
+ return *yyInput++;
+ }
+ if ((c == '+' || c == '-') && isdigit(UCHAR(*(yyInput+1)))) {
+ if ( !isdigit(UCHAR(*(yyInput+2)))
+ || !isdigit(UCHAR(*(yyInput+3)))) {
+ /* GMT+1, GMT-10, etc. */
+ return tZONEwO2;
+ }
+ if ( isdigit(UCHAR(*(yyInput+4)))
+ && !isdigit(UCHAR(*(yyInput+5)))) {
+ /* GMT+1000, etc. */
+ return tZONEwO4;
+ }
+ }
+ }
+ yyInput = bypassSpaces(yyInput);
+ return ret;
+
}
if (c != '(') {
location->last_column = yyInput - info->dateStart;
@@ -2550,169 +2622,75 @@ TclDatelex(
} while (Count > 0);
}
}
-
+
int
-TclClockOldscanObjCmd(
- TCL_UNUSED(void *),
+TclClockFreeScan(
Tcl_Interp *interp, /* Tcl interpreter */
- int objc, /* Count of parameters */
- Tcl_Obj *const *objv) /* Parameters */
+ DateInfo *info) /* Input and result parameters */
{
- Tcl_Obj *result, *resultElement;
- int yr, mo, da;
- DateInfo dateInfo;
- DateInfo* info = &dateInfo;
int status;
- if (objc != 5) {
- Tcl_WrongNumArgs(interp, 1, objv,
- "stringToParse baseYear baseMonth baseDay" );
- return TCL_ERROR;
- }
-
- yyInput = TclGetString(objv[1]);
- dateInfo.dateStart = yyInput;
-
- yyHaveDate = 0;
- if (Tcl_GetIntFromObj(interp, objv[2], &yr) != TCL_OK
- || Tcl_GetIntFromObj(interp, objv[3], &mo) != TCL_OK
- || Tcl_GetIntFromObj(interp, objv[4], &da) != TCL_OK) {
- return TCL_ERROR;
- }
- yyYear = yr; yyMonth = mo; yyDay = da;
-
- yyHaveTime = 0;
- yyHour = 0; yyMinutes = 0; yySeconds = 0; yyMeridian = MER24;
+ #if YYDEBUG
+ /* enable debugging if compiled with YYDEBUG */
+ yydebug = 1;
+ #endif
- yyHaveZone = 0;
- yyTimezone = 0; yyDSTmode = DSTmaybe;
+ /*
+ * yyInput = stringToParse;
+ *
+ * ClockInitDateInfo(info) should be executed to pre-init info;
+ */
- yyHaveOrdinalMonth = 0;
- yyMonthOrdinal = 0;
+ yyDSTmode = DSTmaybe;
- yyHaveDay = 0;
- yyDayOrdinal = 0; yyDayNumber = 0;
+ info->separatrix = "";
- yyHaveRel = 0;
- yyRelMonth = 0; yyRelDay = 0; yyRelSeconds = 0; yyRelPointer = NULL;
+ info->dateStart = yyInput;
- TclNewObj(dateInfo.messages);
- dateInfo.separatrix = "";
- Tcl_IncrRefCount(dateInfo.messages);
+ /* ignore spaces at begin */
+ yyInput = bypassSpaces(yyInput);
- status = yyparse(&dateInfo);
+ /* parse */
+ status = yyparse(info);
if (status == 1) {
- Tcl_SetObjResult(interp, dateInfo.messages);
- Tcl_DecrRefCount(dateInfo.messages);
- Tcl_SetErrorCode(interp, "TCL", "VALUE", "DATE", "PARSE", (char *)NULL);
- return TCL_ERROR;
+ const char *msg = NULL;
+ if (info->errFlags & CLF_HAVEDATE) {
+ msg = "more than one date in string";
+ } else if (info->errFlags & CLF_TIME) {
+ msg = "more than one time of day in string";
+ } else if (info->errFlags & CLF_ZONE) {
+ msg = "more than one time zone in string";
+ } else if (info->errFlags & CLF_DAYOFWEEK) {
+ msg = "more than one weekday in string";
+ } else if (info->errFlags & CLF_ORDINALMONTH) {
+ msg = "more than one ordinal month in string";
+ }
+ if (msg) {
+ Tcl_SetObjResult(interp, Tcl_NewStringObj(msg, -1));
+ Tcl_SetErrorCode(interp, "TCL", "VALUE", "DATE", "MULTIPLE", (char *)NULL);
+ } else {
+ Tcl_SetObjResult(interp,
+ info->messages ? info->messages : Tcl_NewObj());
+ info->messages = NULL;
+ Tcl_SetErrorCode(interp, "TCL", "VALUE", "DATE", "PARSE", (char *)NULL);
+ }
+ status = TCL_ERROR;
} else if (status == 2) {
Tcl_SetObjResult(interp, Tcl_NewStringObj("memory exhausted", -1));
- Tcl_DecrRefCount(dateInfo.messages);
Tcl_SetErrorCode(interp, "TCL", "MEMORY", (char *)NULL);
- return TCL_ERROR;
+ status = TCL_ERROR;
} else if (status != 0) {
Tcl_SetObjResult(interp, Tcl_NewStringObj("Unknown status returned "
"from date parser. Please "
"report this error as a "
"bug in Tcl.", -1));
- Tcl_DecrRefCount(dateInfo.messages);
Tcl_SetErrorCode(interp, "TCL", "BUG", (char *)NULL);
- return TCL_ERROR;
- }
- Tcl_DecrRefCount(dateInfo.messages);
-
- if (yyHaveDate > 1) {
- Tcl_SetObjResult(interp,
- Tcl_NewStringObj("more than one date in string", -1));
- Tcl_SetErrorCode(interp, "TCL", "VALUE", "DATE", "MULTIPLE", (char *)NULL);
- return TCL_ERROR;
- }
- if (yyHaveTime > 1) {
- Tcl_SetObjResult(interp,
- Tcl_NewStringObj("more than one time of day in string", -1));
- Tcl_SetErrorCode(interp, "TCL", "VALUE", "DATE", "MULTIPLE", (char *)NULL);
- return TCL_ERROR;
- }
- if (yyHaveZone > 1) {
- Tcl_SetObjResult(interp,
- Tcl_NewStringObj("more than one time zone in string", -1));
- Tcl_SetErrorCode(interp, "TCL", "VALUE", "DATE", "MULTIPLE", (char *)NULL);
- return TCL_ERROR;
- }
- if (yyHaveDay > 1) {
- Tcl_SetObjResult(interp,
- Tcl_NewStringObj("more than one weekday in string", -1));
- Tcl_SetErrorCode(interp, "TCL", "VALUE", "DATE", "MULTIPLE", (char *)NULL);
- return TCL_ERROR;
+ status = TCL_ERROR;
}
- if (yyHaveOrdinalMonth > 1) {
- Tcl_SetObjResult(interp,
- Tcl_NewStringObj("more than one ordinal month in string", -1));
- Tcl_SetErrorCode(interp, "TCL", "VALUE", "DATE", "MULTIPLE", (char *)NULL);
- return TCL_ERROR;
+ if (info->messages) {
+ Tcl_DecrRefCount(info->messages);
}
-
- TclNewObj(result);
- TclNewObj(resultElement);
- if (yyHaveDate) {
- Tcl_ListObjAppendElement(interp, resultElement,
- Tcl_NewIntObj(yyYear));
- Tcl_ListObjAppendElement(interp, resultElement,
- Tcl_NewIntObj(yyMonth));
- Tcl_ListObjAppendElement(interp, resultElement,
- Tcl_NewIntObj(yyDay));
- }
- Tcl_ListObjAppendElement(interp, result, resultElement);
-
- if (yyHaveTime) {
- Tcl_ListObjAppendElement(interp, result, Tcl_NewIntObj(
- ToSeconds(yyHour, yyMinutes, yySeconds, (MERIDIAN)yyMeridian)));
- } else {
- TclNewObj(resultElement);
- Tcl_ListObjAppendElement(interp, result, resultElement);
- }
-
- TclNewObj(resultElement);
- if (yyHaveZone) {
- Tcl_ListObjAppendElement(interp, resultElement,
- Tcl_NewIntObj(-yyTimezone));
- Tcl_ListObjAppendElement(interp, resultElement,
- Tcl_NewIntObj(1 - yyDSTmode));
- }
- Tcl_ListObjAppendElement(interp, result, resultElement);
-
- TclNewObj(resultElement);
- if (yyHaveRel) {
- Tcl_ListObjAppendElement(interp, resultElement,
- Tcl_NewIntObj(yyRelMonth));
- Tcl_ListObjAppendElement(interp, resultElement,
- Tcl_NewIntObj(yyRelDay));
- Tcl_ListObjAppendElement(interp, resultElement,
- Tcl_NewIntObj(yyRelSeconds));
- }
- Tcl_ListObjAppendElement(interp, result, resultElement);
-
- TclNewObj(resultElement);
- if (yyHaveDay && !yyHaveDate) {
- Tcl_ListObjAppendElement(interp, resultElement,
- Tcl_NewIntObj(yyDayOrdinal));
- Tcl_ListObjAppendElement(interp, resultElement,
- Tcl_NewIntObj(yyDayNumber));
- }
- Tcl_ListObjAppendElement(interp, result, resultElement);
-
- TclNewObj(resultElement);
- if (yyHaveOrdinalMonth) {
- Tcl_ListObjAppendElement(interp, resultElement,
- Tcl_NewIntObj(yyMonthOrdinal));
- Tcl_ListObjAppendElement(interp, resultElement,
- Tcl_NewIntObj(yyMonth));
- }
- Tcl_ListObjAppendElement(interp, result, resultElement);
-
- Tcl_SetObjResult(interp, result);
- return TCL_OK;
+ return status;
}
/*
diff --git a/generic/tclDate.h b/generic/tclDate.h
new file mode 100644
index 0000000..81910ff
--- /dev/null
+++ b/generic/tclDate.h
@@ -0,0 +1,569 @@
+/*
+ * tclDate.h --
+ *
+ * This header file handles common usage of clock primitives
+ * between tclDate.c (yacc), tclClock.c and tclClockFmt.c.
+ *
+ * Copyright (c) 2014 Serg G. Brester (aka sebres)
+ *
+ * See the file "license.terms" for information on usage and redistribution
+ * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
+ */
+
+#ifndef _TCLCLOCK_H
+#define _TCLCLOCK_H
+
+/*
+ * Constants
+ */
+
+#define JULIAN_DAY_POSIX_EPOCH 2440588
+#define GREGORIAN_CHANGE_DATE 2361222
+#define SECONDS_PER_DAY 86400
+#define JULIAN_SEC_POSIX_EPOCH (((Tcl_WideInt) JULIAN_DAY_POSIX_EPOCH) \
+ * SECONDS_PER_DAY)
+#define FOUR_CENTURIES 146097 /* days */
+#define JDAY_1_JAN_1_CE_JULIAN 1721424
+#define JDAY_1_JAN_1_CE_GREGORIAN 1721426
+#define ONE_CENTURY_GREGORIAN 36524 /* days */
+#define FOUR_YEARS 1461 /* days */
+#define ONE_YEAR 365 /* days */
+
+#define RODDENBERRY 1946 /* Another epoch (Hi, Jeff!) */
+
+
+#define CLF_OPTIONAL (1 << 0) /* token is non mandatory */
+#define CLF_POSIXSEC (1 << 1)
+#define CLF_LOCALSEC (1 << 2)
+#define CLF_JULIANDAY (1 << 3)
+#define CLF_TIME (1 << 4)
+#define CLF_ZONE (1 << 5)
+#define CLF_CENTURY (1 << 6)
+#define CLF_DAYOFMONTH (1 << 7)
+#define CLF_DAYOFYEAR (1 << 8)
+#define CLF_MONTH (1 << 9)
+#define CLF_YEAR (1 << 10)
+#define CLF_DAYOFWEEK (1 << 11)
+#define CLF_ISO8601YEAR (1 << 12)
+#define CLF_ISO8601WEAK (1 << 13)
+#define CLF_ISO8601CENTURY (1 << 14)
+
+#define CLF_SIGNED (1 << 15)
+
+/* extra flags used outside of scan/format-tokens too (int, not a short int) */
+#define CLF_RELCONV (1 << 17)
+#define CLF_ORDINALMONTH (1 << 18)
+
+/* On demand (lazy) assemble flags */
+#define CLF_ASSEMBLE_DATE (1 << 28) /* assemble year, month, etc. using julianDay */
+#define CLF_ASSEMBLE_JULIANDAY (1 << 29) /* assemble julianDay using year, month, etc. */
+#define CLF_ASSEMBLE_SECONDS (1 << 30) /* assemble localSeconds (and seconds at end) */
+
+#define CLF_HAVEDATE (CLF_DAYOFMONTH|CLF_MONTH|CLF_YEAR)
+#define CLF_DATE (CLF_JULIANDAY | CLF_DAYOFMONTH | CLF_DAYOFYEAR | \
+ CLF_MONTH | CLF_YEAR | CLF_ISO8601YEAR | \
+ CLF_DAYOFWEEK | CLF_ISO8601WEAK)
+
+#define TCL_MIN_SECONDS -0x00F0000000000000L
+#define TCL_MAX_SECONDS 0x00F0000000000000L
+#define TCL_INV_SECONDS (TCL_MIN_SECONDS-1)
+
+/*
+ * Enumeration of the string literals used in [clock]
+ */
+
+typedef enum ClockLiteral {
+ LIT__NIL,
+ LIT__DEFAULT_FORMAT,
+ LIT_SYSTEM, LIT_CURRENT, LIT_C,
+ LIT_BCE, LIT_CE,
+ LIT_DAYOFMONTH, LIT_DAYOFWEEK, LIT_DAYOFYEAR,
+ LIT_ERA, LIT_GMT, LIT_GREGORIAN,
+ LIT_INTEGER_VALUE_TOO_LARGE,
+ LIT_ISO8601WEEK, LIT_ISO8601YEAR,
+ LIT_JULIANDAY, LIT_LOCALSECONDS,
+ LIT_MONTH,
+ LIT_SECONDS, LIT_TZNAME, LIT_TZOFFSET,
+ LIT_YEAR,
+ LIT_TZDATA,
+ LIT_GETSYSTEMTIMEZONE,
+ LIT_SETUPTIMEZONE,
+ LIT_MCGET,
+ LIT_GETSYSTEMLOCALE, LIT_GETCURRENTLOCALE,
+ LIT_LOCALIZE_FORMAT,
+ LIT__END
+} ClockLiteral;
+
+#define CLOCK_LITERAL_ARRAY(litarr) static const char *const litarr[] = { \
+ "", \
+ "%a %b %d %H:%M:%S %Z %Y", \
+ "system", "current", "C", \
+ "BCE", "CE", \
+ "dayOfMonth", "dayOfWeek", "dayOfYear", \
+ "era", ":GMT", "gregorian", \
+ "integer value too large to represent", \
+ "iso8601Week", "iso8601Year", \
+ "julianDay", "localSeconds", \
+ "month", \
+ "seconds", "tzName", "tzOffset", \
+ "year", \
+ "::tcl::clock::TZData", \
+ "::tcl::clock::GetSystemTimeZone", \
+ "::tcl::clock::SetupTimeZone", \
+ "::tcl::clock::mcget", \
+ "::tcl::clock::GetSystemLocale", "::tcl::clock::mclocale", \
+ "::tcl::clock::LocalizeFormat" \
+}
+
+/*
+ * Enumeration of the msgcat literals used in [clock]
+ */
+
+typedef enum ClockMsgCtLiteral {
+ MCLIT__NIL, /* placeholder */
+ MCLIT_MONTHS_FULL, MCLIT_MONTHS_ABBREV, MCLIT_MONTHS_COMB,
+ MCLIT_DAYS_OF_WEEK_FULL, MCLIT_DAYS_OF_WEEK_ABBREV, MCLIT_DAYS_OF_WEEK_COMB,
+ MCLIT_AM, MCLIT_PM,
+ MCLIT_LOCALE_ERAS,
+ MCLIT_BCE, MCLIT_CE,
+ MCLIT_BCE2, MCLIT_CE2,
+ MCLIT_BCE3, MCLIT_CE3,
+ MCLIT_LOCALE_NUMERALS,
+ MCLIT__END
+} ClockMsgCtLiteral;
+
+#define CLOCK_LOCALE_LITERAL_ARRAY(litarr, pref) static const char *const litarr[] = { \
+ pref "", \
+ pref "MONTHS_FULL", pref "MONTHS_ABBREV", pref "MONTHS_COMB", \
+ pref "DAYS_OF_WEEK_FULL", pref "DAYS_OF_WEEK_ABBREV", pref "DAYS_OF_WEEK_COMB", \
+ pref "AM", pref "PM", \
+ pref "LOCALE_ERAS", \
+ pref "BCE", pref "CE", \
+ pref "b.c.e.", pref "c.e.", \
+ pref "b.c.", pref "a.d.", \
+ pref "LOCALE_NUMERALS", \
+}
+
+/*
+ * Structure containing the fields used in [clock format] and [clock scan]
+ */
+
+#define CLF_CTZ (1 << 4)
+
+typedef struct TclDateFields {
+
+ /* Cacheable fields: */
+
+ 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_WideInt julianDay; /* Julian Day Number in local time zone */
+ int isBce; /* 1 if BCE */
+ 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 */
+ int hour; /* Hours of day (in-between time only calculation) */
+ int minutes; /* Minutes of hour (in-between time only calculation) */
+ Tcl_WideInt secondOfMin; /* Seconds of minute (in-between time only calculation) */
+ Tcl_WideInt secondOfDay; /* Seconds of day (in-between time only calculation) */
+
+ int flags; /* 0 or CLF_CTZ */
+
+ /* Non cacheable fields: */
+
+ Tcl_Obj *tzName; /* Name (or corresponding DST-abbreviation) of the
+ * time zone, if set the refCount is incremented */
+} TclDateFields;
+
+#define ClockCacheableDateFieldsSize \
+ offsetof(TclDateFields, tzName)
+
+/*
+ * Meridian: am, pm, or 24-hour style.
+ */
+
+typedef enum _MERIDIAN {
+ MERam, MERpm, MER24
+} MERIDIAN;
+
+/*
+ * Structure contains return parsed fields.
+ */
+
+typedef struct DateInfo {
+ const char *dateStart;
+ const char *dateInput;
+ const char *dateEnd;
+
+ TclDateFields date;
+
+ int flags; /* Signals parts of date/time get found */
+ int errFlags; /* Signals error (part of date/time found twice) */
+
+ MERIDIAN dateMeridian;
+
+ int dateTimezone;
+ int dateDSTmode;
+
+ Tcl_WideInt dateRelMonth;
+ Tcl_WideInt dateRelDay;
+ Tcl_WideInt dateRelSeconds;
+
+ int dateMonthOrdinalIncr;
+ int dateMonthOrdinal;
+
+ int dateDayOrdinal;
+
+ Tcl_WideInt *dateRelPointer;
+
+ int dateSpaceCount;
+ int dateDigitCount;
+
+ int dateCentury;
+
+ Tcl_Obj* messages; /* Error messages */
+ const char* separatrix; /* String separating messages */
+} DateInfo;
+
+#define yydate (info->date) /* Date fields used for converting */
+
+#define yyDay (info->date.dayOfMonth)
+#define yyMonth (info->date.month)
+#define yyYear (info->date.year)
+
+#define yyHour (info->date.hour)
+#define yyMinutes (info->date.minutes)
+#define yySeconds (info->date.secondOfMin)
+#define yySecondOfDay (info->date.secondOfDay)
+
+#define yyDSTmode (info->dateDSTmode)
+#define yyDayOrdinal (info->dateDayOrdinal)
+#define yyDayOfWeek (info->date.dayOfWeek)
+#define yyMonthOrdinalIncr (info->dateMonthOrdinalIncr)
+#define yyMonthOrdinal (info->dateMonthOrdinal)
+#define yyTimezone (info->dateTimezone)
+#define yyMeridian (info->dateMeridian)
+#define yyRelMonth (info->dateRelMonth)
+#define yyRelDay (info->dateRelDay)
+#define yyRelSeconds (info->dateRelSeconds)
+#define yyRelPointer (info->dateRelPointer)
+#define yyInput (info->dateInput)
+#define yyDigitCount (info->dateDigitCount)
+#define yySpaceCount (info->dateSpaceCount)
+
+static inline void
+ClockInitDateInfo(DateInfo *info) {
+ memset(info, 0, sizeof(DateInfo));
+}
+
+/*
+ * Structure containing the command arguments supplied to [clock format] and [clock scan]
+ */
+
+#define CLF_VALIDATE_S1 (1 << 0)
+#define CLF_VALIDATE_S2 (1 << 1)
+#define CLF_VALIDATE (CLF_VALIDATE_S1|CLF_VALIDATE_S2)
+#define CLF_EXTENDED (1 << 4)
+#define CLF_STRICT (1 << 8)
+#define CLF_LOCALE_USED (1 << 15)
+
+typedef struct ClockFmtScnCmdArgs {
+ void *clientData; /* Opaque pointer to literal pool, etc. */
+ Tcl_Interp *interp; /* Tcl interpreter */
+
+ Tcl_Obj *formatObj; /* Format */
+ Tcl_Obj *localeObj; /* Name of the locale where the time will be expressed. */
+ Tcl_Obj *timezoneObj; /* Default time zone in which the time will be expressed */
+ Tcl_Obj *baseObj; /* Base (scan and add) or clockValue (format) */
+ int flags; /* Flags control scanning */
+
+ Tcl_Obj *mcDictObj; /* Current dictionary of tcl::clock package for given localeObj*/
+} ClockFmtScnCmdArgs;
+
+/* Last-period cache for fast UTC to local and backwards conversion */
+typedef struct ClockLastTZOffs {
+ /* keys */
+ Tcl_Obj *timezoneObj;
+ int changeover;
+ Tcl_WideInt localSeconds;
+ Tcl_WideInt rangesVal[2]; /* Bounds for cached time zone offset */
+ /* values */
+ int tzOffset;
+ Tcl_Obj *tzName; /* Name (abbreviation) of this area in TZ */
+} ClockLastTZOffs;
+
+/*
+ * Structure containing the client data for [clock]
+ */
+
+typedef struct ClockClientData {
+ size_t refCount; /* Number of live references. */
+ Tcl_Obj **literals; /* Pool of object literals (common, locale independent). */
+ Tcl_Obj **mcLiterals; /* Msgcat object literals with mc-keys for search with locale. */
+ Tcl_Obj **mcLitIdxs; /* Msgcat object indices prefixed with _IDX_,
+ * used for quick dictionary search */
+
+ Tcl_Obj *mcDicts; /* Msgcat collection, contains weak pointers to locale
+ * catalogs, and owns it references (onetime referenced) */
+
+ /* Cache for current clock parameters, imparted via "configure" */
+ size_t lastTZEpoch;
+ int currentYearCentury;
+ int yearOfCenturySwitch;
+ int validMinYear;
+ int validMaxYear;
+ double maxJDN;
+
+ Tcl_Obj *systemTimeZone;
+ Tcl_Obj *systemSetupTZData;
+ Tcl_Obj *gmtSetupTimeZoneUnnorm;
+ Tcl_Obj *gmtSetupTimeZone;
+ Tcl_Obj *gmtSetupTZData;
+ Tcl_Obj *gmtTZName;
+ Tcl_Obj *lastSetupTimeZoneUnnorm;
+ Tcl_Obj *lastSetupTimeZone;
+ Tcl_Obj *lastSetupTZData;
+ Tcl_Obj *prevSetupTimeZoneUnnorm;
+ Tcl_Obj *prevSetupTimeZone;
+ Tcl_Obj *prevSetupTZData;
+
+ Tcl_Obj *defaultLocale;
+ Tcl_Obj *defaultLocaleDict;
+ Tcl_Obj *currentLocale;
+ Tcl_Obj *currentLocaleDict;
+ Tcl_Obj *lastUsedLocaleUnnorm;
+ Tcl_Obj *lastUsedLocale;
+ Tcl_Obj *lastUsedLocaleDict;
+ Tcl_Obj *prevUsedLocaleUnnorm;
+ Tcl_Obj *prevUsedLocale;
+ Tcl_Obj *prevUsedLocaleDict;
+
+ /* Cache for last base (last-second fast convert if base/tz not changed) */
+ struct {
+ Tcl_Obj *timezoneObj;
+ TclDateFields date;
+ } lastBase;
+
+ /* Last-period cache for fast UTC to Local and backwards conversion */
+ ClockLastTZOffs lastTZOffsCache[2];
+
+ int defFlags; /* Default flags (from configure), ATM
+ * only CLF_VALIDATE supported */
+} ClockClientData;
+
+#define ClockDefaultYearCentury 2000
+#define ClockDefaultCenturySwitch 38
+
+/*
+ * Clock scan and format facilities.
+ */
+
+#define CLOCK_FMT_SCN_STORAGE_GC_SIZE 32
+
+#define CLOCK_MIN_TOK_CHAIN_BLOCK_SIZE 2
+
+typedef struct ClockScanToken ClockScanToken;
+
+
+typedef int ClockScanTokenProc(
+ ClockFmtScnCmdArgs *opts,
+ DateInfo *info,
+ ClockScanToken *tok);
+
+
+typedef enum _CLCKTOK_TYPE {
+ CTOKT_INT = 1, CTOKT_WIDE, CTOKT_PARSER, CTOKT_SPACE, CTOKT_WORD, CTOKT_CHAR,
+ CFMTT_PROC
+} CLCKTOK_TYPE;
+
+typedef struct ClockScanTokenMap {
+ unsigned short int type;
+ unsigned short int flags;
+ unsigned short int clearFlags;
+ unsigned short int minSize;
+ unsigned short int maxSize;
+ unsigned short int offs;
+ ClockScanTokenProc *parser;
+ const void *data;
+} ClockScanTokenMap;
+
+struct ClockScanToken {
+ ClockScanTokenMap *map;
+ struct {
+ const char *start;
+ const char *end;
+ } tokWord;
+ unsigned short int endDistance;
+ unsigned short int lookAhMin;
+ unsigned short int lookAhMax;
+ unsigned short int lookAhTok;
+};
+
+
+#define MIN_FMT_RESULT_BLOCK_ALLOC 80
+#define MIN_FMT_RESULT_BLOCK_DELTA 0
+/* Maximal permitted threshold (buffer size > result size) in percent,
+ * to directly return the buffer without reallocate */
+#define MAX_FMT_RESULT_THRESHOLD 2
+
+typedef struct DateFormat {
+ char *resMem;
+ char *resEnd;
+ char *output;
+
+ TclDateFields date;
+
+ Tcl_Obj *localeEra;
+} DateFormat;
+
+#define CLFMT_INCR (1 << 3)
+#define CLFMT_DECR (1 << 4)
+#define CLFMT_CALC (1 << 5)
+#define CLFMT_LOCALE_INDX (1 << 8)
+
+typedef struct ClockFormatToken ClockFormatToken;
+
+typedef int ClockFormatTokenProc(
+ ClockFmtScnCmdArgs *opts,
+ DateFormat *dateFmt,
+ ClockFormatToken *tok,
+ int *val);
+
+typedef struct ClockFormatTokenMap {
+ unsigned short int type;
+ const char *tostr;
+ unsigned short int width;
+ unsigned short int flags;
+ unsigned short int divider;
+ unsigned short int divmod;
+ unsigned short int offs;
+ ClockFormatTokenProc *fmtproc;
+ void *data;
+} ClockFormatTokenMap;
+
+struct ClockFormatToken {
+ ClockFormatTokenMap *map;
+ struct {
+ const char *start;
+ const char *end;
+ } tokWord;
+};
+
+
+typedef struct ClockFmtScnStorage ClockFmtScnStorage;
+
+struct ClockFmtScnStorage {
+ int objRefCount; /* Reference count shared across threads */
+ ClockScanToken *scnTok;
+ unsigned int scnTokC;
+ unsigned int scnSpaceCount; /* Count of mandatory spaces used in format */
+ ClockFormatToken *fmtTok;
+ unsigned int fmtTokC;
+#if CLOCK_FMT_SCN_STORAGE_GC_SIZE > 0
+ ClockFmtScnStorage *nextPtr;
+ ClockFmtScnStorage *prevPtr;
+#endif
+ size_t fmtMinAlloc;
+#if 0
+ +Tcl_HashEntry hashEntry /* ClockFmtScnStorage is a derivate of Tcl_HashEntry,
+ * stored by offset +sizeof(self) */
+#endif
+};
+
+/*
+ * Clock macros.
+ */
+
+/*
+ * Extracts Julian day and seconds of the day from posix seconds (tm).
+ */
+#define ClockExtractJDAndSODFromSeconds(jd, sod, tm) \
+ if (1) { \
+ jd = (tm + JULIAN_SEC_POSIX_EPOCH); \
+ if (jd >= SECONDS_PER_DAY || jd <= -SECONDS_PER_DAY) { \
+ jd /= SECONDS_PER_DAY; \
+ sod = (int)(tm % SECONDS_PER_DAY); \
+ } else { \
+ sod = (int)jd, jd = 0; \
+ } \
+ if (sod < 0) { \
+ sod += SECONDS_PER_DAY; \
+ /* JD is affected, if switched into negative (avoid 24 hours difference) */ \
+ if (jd <= 0) { \
+ jd--; \
+ } \
+ } \
+ }
+
+/*
+ * Prototypes of module functions.
+ */
+
+MODULE_SCOPE int ToSeconds(int Hours, int Minutes,
+ int Seconds, MERIDIAN Meridian);
+MODULE_SCOPE int IsGregorianLeapYear(TclDateFields *);
+MODULE_SCOPE void
+ GetJulianDayFromEraYearWeekDay(
+ TclDateFields *fields, int changeover);
+MODULE_SCOPE void
+ GetJulianDayFromEraYearMonthDay(
+ TclDateFields *fields, int changeover);
+MODULE_SCOPE void
+ GetJulianDayFromEraYearDay(
+ TclDateFields *fields, int changeover);
+MODULE_SCOPE int ConvertUTCToLocal(void *clientData, Tcl_Interp *,
+ TclDateFields *, Tcl_Obj *timezoneObj, int);
+MODULE_SCOPE Tcl_Obj *
+ LookupLastTransition(Tcl_Interp *, Tcl_WideInt,
+ Tcl_Size, Tcl_Obj *const *, Tcl_WideInt *rangesVal);
+
+MODULE_SCOPE int TclClockFreeScan(Tcl_Interp *interp, DateInfo *info);
+
+/* tclClock.c module declarations */
+
+MODULE_SCOPE Tcl_Obj *
+ ClockSetupTimeZone(void *clientData,
+ Tcl_Interp *interp, Tcl_Obj *timezoneObj);
+
+MODULE_SCOPE Tcl_Obj *
+ ClockMCDict(ClockFmtScnCmdArgs *opts);
+MODULE_SCOPE Tcl_Obj *
+ ClockMCGet(ClockFmtScnCmdArgs *opts, int mcKey);
+MODULE_SCOPE Tcl_Obj *
+ ClockMCGetIdx(ClockFmtScnCmdArgs *opts, int mcKey);
+MODULE_SCOPE int ClockMCSetIdx(ClockFmtScnCmdArgs *opts, int mcKey,
+ Tcl_Obj *valObj);
+
+/* tclClockFmt.c module declarations */
+
+
+MODULE_SCOPE char *
+ TclItoAw(char *buf, int val, char padchar, unsigned short int width);
+
+MODULE_SCOPE Tcl_Obj*
+ ClockFrmObjGetLocFmtKey(Tcl_Interp *interp,
+ Tcl_Obj *objPtr);
+
+MODULE_SCOPE ClockFmtScnStorage *
+ Tcl_GetClockFrmScnFromObj(Tcl_Interp *interp,
+ Tcl_Obj *objPtr);
+MODULE_SCOPE Tcl_Obj *
+ ClockLocalizeFormat(ClockFmtScnCmdArgs *opts);
+
+MODULE_SCOPE int ClockScan(DateInfo *info,
+ Tcl_Obj *strObj, ClockFmtScnCmdArgs *opts);
+
+MODULE_SCOPE int ClockFormat(DateFormat *dateFmt,
+ ClockFmtScnCmdArgs *opts);
+
+MODULE_SCOPE void ClockFrmScnClearCaches(void);
+
+#endif /* _TCLCLOCK_H */
diff --git a/generic/tclDictObj.c b/generic/tclDictObj.c
index 8b7bc3d..7ba981b 100644
--- a/generic/tclDictObj.c
+++ b/generic/tclDictObj.c
@@ -2074,6 +2074,60 @@ DictSizeCmd(
/*
*----------------------------------------------------------------------
*
+ * TclDictObjSmartRef --
+ *
+ * This function returns new tcl-object with the smart reference to
+ * dictionary object.
+ *
+ * Object returned with this function is a smart reference (pointer),
+ * so new object of type tclDictType, that directly references given
+ * dictionary object (with internally increased refCount).
+ *
+ * The usage of such pointer objects allows to hold more as one
+ * reference to the same real dictionary object, allows to make a pointer
+ * to part of another dictionary, allows to change the dictionary without
+ * regarding of the "shared" state of the dictionary object.
+ *
+ * Prevents "called with shared object" exception if object is multiple
+ * referenced.
+ *
+ * Results:
+ * The newly create object (contains smart reference) is returned.
+ * The returned object has a ref count of 0.
+ *
+ * Side effects:
+ * Increases ref count of the referenced dictionary.
+ *
+ *----------------------------------------------------------------------
+ */
+
+Tcl_Obj *
+TclDictObjSmartRef(
+ Tcl_Interp *interp,
+ Tcl_Obj *dictPtr)
+{
+ Tcl_Obj *result;
+ Dict *dict;
+
+ if (dictPtr->typePtr != &tclDictType
+ && SetDictFromAny(interp, dictPtr) != TCL_OK) {
+ return NULL;
+ }
+
+ DictGetInternalRep(dictPtr, dict);
+
+ result = Tcl_NewObj();
+ DictSetInternalRep(result, dict);
+ dict->refCount++;
+ result->internalRep.twoPtrValue.ptr2 = NULL;
+ result->typePtr = &tclDictType;
+
+ return result;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
* DictExistsCmd --
*
* This function implements the "dict exists" Tcl command. See the user
diff --git a/generic/tclGetDate.y b/generic/tclGetDate.y
index dc30d51..077d751 100644
--- a/generic/tclGetDate.y
+++ b/generic/tclGetDate.y
@@ -7,8 +7,9 @@
* only used when doing free-form date parsing, an ill-defined process
* anyway.
*
- * Copyright (c) 1992-1995 Karl Lehenbauer & Mark Diekhans.
- * Copyright (c) 1995-1997 Sun Microsystems, Inc.
+ * Copyright © 1992-1995 Karl Lehenbauer & Mark Diekhans.
+ * Copyright © 1995-1997 Sun Microsystems, Inc.
+ * Copyright © 2015 Sergey G. Brester aka sebres.
*
* See the file "license.terms" for information on usage and redistribution of
* this file, and for a DISCLAIMER OF ALL WARRANTIES.
@@ -27,8 +28,9 @@
* This file is generated from a yacc grammar defined in the file
* tclGetDate.y. It should not be edited directly.
*
- * Copyright (c) 1992-1995 Karl Lehenbauer & Mark Diekhans.
- * Copyright (c) 1995-1997 Sun Microsystems, Inc.
+ * Copyright © 1992-1995 Karl Lehenbauer & Mark Diekhans.
+ * Copyright © 1995-1997 Sun Microsystems, Inc.
+ * Copyright © 2015 Sergey G. Brester aka sebres.
*
* See the file "license.terms" for information on usage and redistribution of
* this file, and for a DISCLAIMER OF ALL WARRANTIES.
@@ -45,86 +47,20 @@
#pragma warning( disable : 4102 )
#endif /* _MSC_VER */
-/*
- * Meridian: am, pm, or 24-hour style.
- */
-
-typedef enum _MERIDIAN {
- MERam, MERpm, MER24
-} MERIDIAN;
+#if 0
+#define YYDEBUG 1
+#endif
/*
* yyparse will accept a 'struct DateInfo' as its parameter; that's where the
* parsed fields will be returned.
*/
-typedef struct DateInfo {
-
- Tcl_Obj* messages; /* Error messages */
- const char* separatrix; /* String separating messages */
-
- time_t dateYear;
- time_t dateMonth;
- time_t dateDay;
- int dateHaveDate;
-
- time_t dateHour;
- time_t dateMinutes;
- time_t dateSeconds;
- MERIDIAN dateMeridian;
- int dateHaveTime;
-
- time_t dateTimezone;
- int dateDSTmode;
- int dateHaveZone;
-
- time_t dateRelMonth;
- time_t dateRelDay;
- time_t dateRelSeconds;
- int dateHaveRel;
-
- time_t dateMonthOrdinal;
- int dateHaveOrdinalMonth;
-
- time_t dateDayOrdinal;
- time_t dateDayNumber;
- int dateHaveDay;
-
- const char *dateStart;
- const char *dateInput;
- time_t *dateRelPointer;
-
- int dateDigitCount;
-} DateInfo;
+#include "tclDate.h"
#define YYMALLOC Tcl_Alloc
#define YYFREE(x) (Tcl_Free((void*) (x)))
-#define yyDSTmode (info->dateDSTmode)
-#define yyDayOrdinal (info->dateDayOrdinal)
-#define yyDayNumber (info->dateDayNumber)
-#define yyMonthOrdinal (info->dateMonthOrdinal)
-#define yyHaveDate (info->dateHaveDate)
-#define yyHaveDay (info->dateHaveDay)
-#define yyHaveOrdinalMonth (info->dateHaveOrdinalMonth)
-#define yyHaveRel (info->dateHaveRel)
-#define yyHaveTime (info->dateHaveTime)
-#define yyHaveZone (info->dateHaveZone)
-#define yyTimezone (info->dateTimezone)
-#define yyDay (info->dateDay)
-#define yyMonth (info->dateMonth)
-#define yyYear (info->dateYear)
-#define yyHour (info->dateHour)
-#define yyMinutes (info->dateMinutes)
-#define yySeconds (info->dateSeconds)
-#define yyMeridian (info->dateMeridian)
-#define yyRelMonth (info->dateRelMonth)
-#define yyRelDay (info->dateRelDay)
-#define yyRelSeconds (info->dateRelSeconds)
-#define yyRelPointer (info->dateRelPointer)
-#define yyInput (info->dateInput)
-#define yyDigitCount (info->dateDigitCount)
-
#define EPOCH 1970
#define START_OF_TIME 1902
#define END_OF_TIME 2037
@@ -136,18 +72,24 @@ typedef struct DateInfo {
#define TM_YEAR_BASE 1900
-#define HOUR(x) ((int) (60 * (x)))
-#define SECSPERDAY (24L * 60L * 60L)
+#define HOUR(x) ((60 * (int)(x)))
#define IsLeapYear(x) (((x) % 4 == 0) && ((x) % 100 != 0 || (x) % 400 == 0))
+#define yyIncrFlags(f) \
+ do { \
+ info->errFlags |= (info->flags & (f)); \
+ if (info->errFlags) { YYABORT; } \
+ info->flags |= (f); \
+ } while (0);
+
/*
* An entry in the lexical lookup table.
*/
-typedef struct _TABLE {
+typedef struct {
const char *name;
int type;
- time_t value;
+ int value;
} TABLE;
/*
@@ -161,7 +103,7 @@ typedef enum _DSTMODE {
%}
%union {
- time_t Number;
+ long long Number;
enum _MERIDIAN Meridian;
}
@@ -172,12 +114,10 @@ typedef enum _DSTMODE {
*/
static int LookupWord(YYSTYPE* yylvalPtr, char *buff);
- static void TclDateerror(YYLTYPE* location,
+static void TclDateerror(YYLTYPE* location,
DateInfo* info, const char *s);
- static int TclDatelex(YYSTYPE* yylvalPtr, YYLTYPE* location,
+static int TclDatelex(YYSTYPE* yylvalPtr, YYLTYPE* location,
DateInfo* info);
-static time_t ToSeconds(time_t Hours, time_t Minutes,
- time_t Seconds, MERIDIAN Meridian);
MODULE_SCOPE int yyparse(DateInfo*);
%}
@@ -191,14 +131,18 @@ MODULE_SCOPE int yyparse(DateInfo*);
%token tMONTH_UNIT
%token tSTARDATE
%token tSEC_UNIT
-%token tSNUMBER
%token tUNUMBER
%token tZONE
+%token tZONEwO4
+%token tZONEwO2
%token tEPOCH
%token tDST
-%token tISOBASE
+%token tISOBAS8
+%token tISOBAS6
+%token tISOBASL
%token tDAY_UNIT
%token tNEXT
+%token SP
%type <Number> tDAY
%type <Number> tDAYZONE
@@ -206,10 +150,14 @@ MODULE_SCOPE int yyparse(DateInfo*);
%type <Number> tMONTH_UNIT
%type <Number> tDST
%type <Number> tSEC_UNIT
-%type <Number> tSNUMBER
%type <Number> tUNUMBER
+%type <Number> INTNUM
%type <Number> tZONE
-%type <Number> tISOBASE
+%type <Number> tZONEwO4
+%type <Number> tZONEwO2
+%type <Number> tISOBAS8
+%type <Number> tISOBAS6
+%type <Number> tISOBASL
%type <Number> tDAY_UNIT
%type <Number> unit
%type <Number> sign
@@ -222,100 +170,121 @@ MODULE_SCOPE int yyparse(DateInfo*);
spec : /* NULL */
| spec item
+ /* | spec SP item */
;
item : time {
- yyHaveTime++;
+ yyIncrFlags(CLF_TIME);
}
| zone {
- yyHaveZone++;
+ yyIncrFlags(CLF_ZONE);
}
| date {
- yyHaveDate++;
+ yyIncrFlags(CLF_HAVEDATE);
}
| ordMonth {
- yyHaveOrdinalMonth++;
+ yyIncrFlags(CLF_ORDINALMONTH);
}
| day {
- yyHaveDay++;
+ yyIncrFlags(CLF_DAYOFWEEK);
}
| relspec {
- yyHaveRel++;
+ info->flags |= CLF_RELCONV;
}
| iso {
- yyHaveTime++;
- yyHaveDate++;
+ yyIncrFlags(CLF_TIME|CLF_HAVEDATE);
}
| trek {
- yyHaveTime++;
- yyHaveDate++;
- yyHaveRel++;
+ yyIncrFlags(CLF_TIME|CLF_HAVEDATE);
+ info->flags |= CLF_RELCONV;
}
- | number
+ | numitem
;
-time : tUNUMBER tMERIDIAN {
+iextime : tUNUMBER ':' tUNUMBER ':' tUNUMBER {
yyHour = $1;
- yyMinutes = 0;
- yySeconds = 0;
- yyMeridian = $2;
+ yyMinutes = $3;
+ yySeconds = $5;
}
- | tUNUMBER ':' tUNUMBER o_merid {
+ | tUNUMBER ':' tUNUMBER {
yyHour = $1;
yyMinutes = $3;
yySeconds = 0;
- yyMeridian = $4;
}
- | tUNUMBER ':' tUNUMBER ':' tUNUMBER o_merid {
+ ;
+time : tUNUMBER tMERIDIAN {
yyHour = $1;
- yyMinutes = $3;
- yySeconds = $5;
- yyMeridian = $6;
+ yyMinutes = 0;
+ yySeconds = 0;
+ yyMeridian = $2;
+ }
+ | iextime o_merid {
+ yyMeridian = $2;
}
;
zone : tZONE tDST {
yyTimezone = $1;
- if (yyTimezone > HOUR( 12)) yyTimezone -= HOUR(100);
yyDSTmode = DSTon;
}
| tZONE {
yyTimezone = $1;
- if (yyTimezone > HOUR( 12)) yyTimezone -= HOUR(100);
yyDSTmode = DSToff;
}
| tDAYZONE {
yyTimezone = $1;
yyDSTmode = DSTon;
}
- | sign tUNUMBER {
+ | tZONEwO4 sign INTNUM { /* GMT+0100, GMT-1000, etc. */
+ yyTimezone = $1 - $2*($3 % 100 + ($3 / 100) * 60);
+ yyDSTmode = DSToff;
+ }
+ | tZONEwO2 sign INTNUM { /* GMT+1, GMT-10, etc. */
+ yyTimezone = $1 - $2*($3 * 60);
+ yyDSTmode = DSToff;
+ }
+ | sign INTNUM { /* +0100, -0100 */
yyTimezone = -$1*($2 % 100 + ($2 / 100) * 60);
yyDSTmode = DSToff;
}
;
+comma : ','
+ | ',' SP
+ ;
+
day : tDAY {
yyDayOrdinal = 1;
- yyDayNumber = $1;
+ yyDayOfWeek = $1;
}
- | tDAY ',' {
+ | tDAY comma {
yyDayOrdinal = 1;
- yyDayNumber = $1;
+ yyDayOfWeek = $1;
}
| tUNUMBER tDAY {
yyDayOrdinal = $1;
- yyDayNumber = $2;
+ yyDayOfWeek = $2;
+ }
+ | sign SP tUNUMBER tDAY {
+ yyDayOrdinal = $1 * $3;
+ yyDayOfWeek = $4;
}
| sign tUNUMBER tDAY {
yyDayOrdinal = $1 * $2;
- yyDayNumber = $3;
+ yyDayOfWeek = $3;
}
| tNEXT tDAY {
yyDayOrdinal = 2;
- yyDayNumber = $2;
+ yyDayOfWeek = $2;
}
;
+iexdate : tUNUMBER '-' tUNUMBER '-' tUNUMBER {
+ yyMonth = $3;
+ yyDay = $5;
+ yyYear = $1;
+ }
+ ;
date : tUNUMBER '/' tUNUMBER {
yyMonth = $1;
yyDay = $3;
@@ -325,26 +294,17 @@ date : tUNUMBER '/' tUNUMBER {
yyDay = $3;
yyYear = $5;
}
- | tISOBASE {
- yyYear = $1 / 10000;
- yyMonth = ($1 % 10000)/100;
- yyDay = $1 % 100;
- }
+ | isodate
| tUNUMBER '-' tMONTH '-' tUNUMBER {
yyDay = $1;
yyMonth = $3;
yyYear = $5;
}
- | tUNUMBER '-' tUNUMBER '-' tUNUMBER {
- yyMonth = $3;
- yyDay = $5;
- yyYear = $1;
- }
| tMONTH tUNUMBER {
yyMonth = $1;
yyDay = $2;
}
- | tMONTH tUNUMBER ',' tUNUMBER {
+ | tMONTH tUNUMBER comma tUNUMBER {
yyMonth = $1;
yyDay = $2;
yyYear = $4;
@@ -366,44 +326,38 @@ date : tUNUMBER '/' tUNUMBER {
;
ordMonth: tNEXT tMONTH {
- yyMonthOrdinal = 1;
- yyMonth = $2;
+ yyMonthOrdinalIncr = 1;
+ yyMonthOrdinal = $2;
}
| tNEXT tUNUMBER tMONTH {
- yyMonthOrdinal = $2;
- yyMonth = $3;
+ yyMonthOrdinalIncr = $2;
+ yyMonthOrdinal = $3;
}
;
-iso : tUNUMBER '-' tUNUMBER '-' tUNUMBER tZONE
- tUNUMBER ':' tUNUMBER ':' tUNUMBER {
- if ($6 != HOUR( 7) + HOUR(100)) YYABORT;
- yyYear = $1;
- yyMonth = $3;
- yyDay = $5;
- yyHour = $7;
- yyMinutes = $9;
- yySeconds = $11;
- }
- | tISOBASE tZONE tISOBASE {
- if ($2 != HOUR( 7) + HOUR(100)) YYABORT;
+isosep : 'T'|SP
+ ;
+isodate : tISOBAS8 { /* YYYYMMDD */
yyYear = $1 / 10000;
yyMonth = ($1 % 10000)/100;
yyDay = $1 % 100;
- yyHour = $3 / 10000;
- yyMinutes = ($3 % 10000)/100;
- yySeconds = $3 % 100;
}
- | tISOBASE tZONE tUNUMBER ':' tUNUMBER ':' tUNUMBER {
- if ($2 != HOUR( 7) + HOUR(100)) YYABORT;
+ | tISOBAS6 { /* YYMMDD */
yyYear = $1 / 10000;
yyMonth = ($1 % 10000)/100;
yyDay = $1 % 100;
- yyHour = $3;
- yyMinutes = $5;
- yySeconds = $7;
}
- | tISOBASE tISOBASE {
+ | iexdate
+ ;
+isotime : tISOBAS6 {
+ yyHour = $1 / 10000;
+ yyMinutes = ($1 % 10000)/100;
+ yySeconds = $1 % 100;
+ }
+ | iextime
+ ;
+iso : isodate isosep isotime
+ | tISOBASL tISOBAS6 { /* YYYYMMDDhhmmss */
yyYear = $1 / 10000;
yyMonth = ($1 % 10000)/100;
yyDay = $1 % 100;
@@ -411,9 +365,18 @@ iso : tUNUMBER '-' tUNUMBER '-' tUNUMBER tZONE
yyMinutes = ($2 % 10000)/100;
yySeconds = $2 % 100;
}
+ | tISOBASL tUNUMBER { /* YYYYMMDDhhmm */
+ if (yyDigitCount != 4) YYABORT; /* normally unreached */
+ yyYear = $1 / 10000;
+ yyMonth = ($1 % 10000)/100;
+ yyDay = $1 % 100;
+ yyHour = $2 / 100;
+ yyMinutes = ($2 % 100);
+ yySeconds = 0;
+ }
;
-trek : tSTARDATE tUNUMBER '.' tUNUMBER {
+trek : tSTARDATE INTNUM '.' tUNUMBER {
/*
* Offset computed year by -377 so that the returned years will be
* in a range accessible with a 32 bit clock seconds value.
@@ -423,7 +386,7 @@ trek : tSTARDATE tUNUMBER '.' tUNUMBER {
yyDay = 1;
yyMonth = 1;
yyRelDay += (($2%1000)*(365 + IsLeapYear(yyYear)))/1000;
- yyRelSeconds += $4 * 144 * 60;
+ yyRelSeconds += $4 * (144LL * 60LL);
}
;
@@ -435,16 +398,19 @@ relspec : relunits tAGO {
| relunits
;
-relunits : sign tUNUMBER unit {
+relunits : sign SP INTNUM unit {
+ *yyRelPointer += $1 * $3 * $4;
+ }
+ | sign INTNUM unit {
*yyRelPointer += $1 * $2 * $3;
}
- | tUNUMBER unit {
+ | INTNUM unit {
*yyRelPointer += $1 * $2;
}
| tNEXT unit {
*yyRelPointer += $2;
}
- | tNEXT tUNUMBER unit {
+ | tNEXT INTNUM unit {
*yyRelPointer += $2 * $3;
}
| unit {
@@ -474,11 +440,22 @@ unit : tSEC_UNIT {
}
;
-number : tUNUMBER {
- if (yyHaveTime && yyHaveDate && !yyHaveRel) {
+INTNUM : tUNUMBER {
+ $$ = $1;
+ }
+ | tISOBAS6 {
+ $$ = $1;
+ }
+ | tISOBAS8 {
+ $$ = $1;
+ }
+ ;
+
+numitem : tUNUMBER {
+ if ((info->flags & (CLF_TIME|CLF_HAVEDATE|CLF_RELCONV)) == (CLF_TIME|CLF_HAVEDATE)) {
yyYear = $1;
} else {
- yyHaveTime++;
+ yyIncrFlags(CLF_TIME);
if (yyDigitCount <= 2) {
yyHour = $1;
yyMinutes = 0;
@@ -563,20 +540,6 @@ static const TABLE OtherTable[] = {
{ "last", tUNUMBER, -1 },
{ "this", tSEC_UNIT, 0 },
{ "next", tNEXT, 1 },
-#if 0
- { "first", tUNUMBER, 1 },
- { "second", tUNUMBER, 2 },
- { "third", tUNUMBER, 3 },
- { "fourth", tUNUMBER, 4 },
- { "fifth", tUNUMBER, 5 },
- { "sixth", tUNUMBER, 6 },
- { "seventh", tUNUMBER, 7 },
- { "eighth", tUNUMBER, 8 },
- { "ninth", tUNUMBER, 9 },
- { "tenth", tUNUMBER, 10 },
- { "eleventh", tUNUMBER, 11 },
- { "twelfth", tUNUMBER, 12 },
-#endif
{ "ago", tAGO, 1 },
{ "epoch", tEPOCH, 0 },
{ "stardate", tSTARDATE, 0 },
@@ -676,34 +639,44 @@ static const TABLE TimezoneTable[] = {
*/
static const TABLE MilitaryTable[] = {
- { "a", tZONE, -HOUR( 1) + HOUR(100) },
- { "b", tZONE, -HOUR( 2) + HOUR(100) },
- { "c", tZONE, -HOUR( 3) + HOUR(100) },
- { "d", tZONE, -HOUR( 4) + HOUR(100) },
- { "e", tZONE, -HOUR( 5) + HOUR(100) },
- { "f", tZONE, -HOUR( 6) + HOUR(100) },
- { "g", tZONE, -HOUR( 7) + HOUR(100) },
- { "h", tZONE, -HOUR( 8) + HOUR(100) },
- { "i", tZONE, -HOUR( 9) + HOUR(100) },
- { "k", tZONE, -HOUR(10) + HOUR(100) },
- { "l", tZONE, -HOUR(11) + HOUR(100) },
- { "m", tZONE, -HOUR(12) + HOUR(100) },
- { "n", tZONE, HOUR( 1) + HOUR(100) },
- { "o", tZONE, HOUR( 2) + HOUR(100) },
- { "p", tZONE, HOUR( 3) + HOUR(100) },
- { "q", tZONE, HOUR( 4) + HOUR(100) },
- { "r", tZONE, HOUR( 5) + HOUR(100) },
- { "s", tZONE, HOUR( 6) + HOUR(100) },
- { "t", tZONE, HOUR( 7) + HOUR(100) },
- { "u", tZONE, HOUR( 8) + HOUR(100) },
- { "v", tZONE, HOUR( 9) + HOUR(100) },
- { "w", tZONE, HOUR( 10) + HOUR(100) },
- { "x", tZONE, HOUR( 11) + HOUR(100) },
- { "y", tZONE, HOUR( 12) + HOUR(100) },
- { "z", tZONE, HOUR( 0) + HOUR(100) },
+ { "a", tZONE, -HOUR( 1) },
+ { "b", tZONE, -HOUR( 2) },
+ { "c", tZONE, -HOUR( 3) },
+ { "d", tZONE, -HOUR( 4) },
+ { "e", tZONE, -HOUR( 5) },
+ { "f", tZONE, -HOUR( 6) },
+ { "g", tZONE, -HOUR( 7) },
+ { "h", tZONE, -HOUR( 8) },
+ { "i", tZONE, -HOUR( 9) },
+ { "k", tZONE, -HOUR(10) },
+ { "l", tZONE, -HOUR(11) },
+ { "m", tZONE, -HOUR(12) },
+ { "n", tZONE, HOUR( 1) },
+ { "o", tZONE, HOUR( 2) },
+ { "p", tZONE, HOUR( 3) },
+ { "q", tZONE, HOUR( 4) },
+ { "r", tZONE, HOUR( 5) },
+ { "s", tZONE, HOUR( 6) },
+ { "t", tZONE, HOUR( 7) },
+ { "u", tZONE, HOUR( 8) },
+ { "v", tZONE, HOUR( 9) },
+ { "w", tZONE, HOUR( 10) },
+ { "x", tZONE, HOUR( 11) },
+ { "y", tZONE, HOUR( 12) },
+ { "z", tZONE, HOUR( 0) },
{ NULL, 0, 0 }
};
+static inline const char *
+bypassSpaces(
+ const char *s)
+{
+ while (TclIsSpaceProc(*s)) {
+ s++;
+ }
+ return s;
+}
+
/*
* Dump error messages in the bit bucket.
*/
@@ -715,6 +688,9 @@ TclDateerror(
const char *s)
{
Tcl_Obj* t;
+ if (!infoPtr->messages) {
+ TclNewObj(infoPtr->messages);
+ }
Tcl_AppendToObj(infoPtr->messages, infoPtr->separatrix, -1);
Tcl_AppendToObj(infoPtr->messages, s, -1);
Tcl_AppendToObj(infoPtr->messages, " (characters ", -1);
@@ -731,11 +707,11 @@ TclDateerror(
infoPtr->separatrix = "\n";
}
-static time_t
+int
ToSeconds(
- time_t Hours,
- time_t Minutes,
- time_t Seconds,
+ int Hours,
+ int Minutes,
+ int Seconds,
MERIDIAN Meridian)
{
if (Minutes < 0 || Minutes > 59 || Seconds < 0 || Seconds > 59) {
@@ -746,17 +722,17 @@ ToSeconds(
if (Hours < 0 || Hours > 23) {
return -1;
}
- return (Hours * 60L + Minutes) * 60L + Seconds;
+ return (Hours * 60 + Minutes) * 60 + Seconds;
case MERam:
if (Hours < 1 || Hours > 12) {
return -1;
}
- return ((Hours % 12) * 60L + Minutes) * 60L + Seconds;
+ return ((Hours % 12) * 60 + Minutes) * 60 + Seconds;
case MERpm:
if (Hours < 1 || Hours > 12) {
return -1;
}
- return (((Hours % 12) + 12) * 60L + Minutes) * 60L + Seconds;
+ return (((Hours % 12) + 12) * 60 + Minutes) * 60 + Seconds;
}
return -1; /* Should never be reached */
}
@@ -777,11 +753,11 @@ LookupWord(
Tcl_UtfToLower(buff);
- if (strcmp(buff, "am") == 0 || strcmp(buff, "a.m.") == 0) {
+ if (*buff == 'a' && (strcmp(buff, "am") == 0 || strcmp(buff, "a.m.") == 0)) {
yylvalPtr->Meridian = MERam;
return tMERIDIAN;
}
- if (strcmp(buff, "pm") == 0 || strcmp(buff, "p.m.") == 0) {
+ if (*buff == 'p' && (strcmp(buff, "pm") == 0 || strcmp(buff, "p.m.") == 0)) {
yylvalPtr->Meridian = MERpm;
return tMERIDIAN;
}
@@ -895,50 +871,114 @@ TclDatelex(
char *p;
char buff[20];
int Count;
+ const char *tokStart;
location->first_column = yyInput - info->dateStart;
for ( ; ; ) {
- while (TclIsSpaceProcM(*yyInput)) {
- yyInput++;
+
+ if (isspace(UCHAR(*yyInput))) {
+ yyInput = bypassSpaces(yyInput);
+ /* ignore space at end of text and before some words */
+ c = *yyInput;
+ if (c != '\0' && !isalpha(UCHAR(c))) {
+ return SP;
+ }
}
+ tokStart = yyInput;
if (isdigit(UCHAR(c = *yyInput))) { /* INTL: digit */
+
/*
* Convert the string into a number; count the number of digits.
*/
-
- Count = 0;
- for (yylvalPtr->Number = 0;
- isdigit(UCHAR(c = *yyInput++)); ) { /* INTL: digit */
- yylvalPtr->Number = 10 * yylvalPtr->Number + c - '0';
- Count++;
+ long long num = c - '0';
+ p = (char *)yyInput;
+ while (isdigit(UCHAR(c = *(++p)))) {
+ if (num >= 0) {
+ num *= 10; num += c - '0';
+ }
}
- yyInput--;
- yyDigitCount = Count;
+ yylvalPtr->Number = num;
+ yyDigitCount = p - yyInput;
+ yyInput = p;
/*
* A number with 6 or more digits is considered an ISO 8601 base.
*/
- if (Count >= 6) {
- location->last_column = yyInput - info->dateStart - 1;
- return tISOBASE;
- } else {
- location->last_column = yyInput - info->dateStart - 1;
- return tUNUMBER;
+ location->last_column = yyInput - info->dateStart - 1;
+ if (yyDigitCount >= 6) {
+ if (yyDigitCount == 14 || yyDigitCount == 12) {
+ /* long form of ISO 8601 (without separator), either
+ * YYYYMMDDhhmmss or YYYYMMDDhhmm, so reduce to date
+ * (8 chars is isodate) */
+ p = (char *)tokStart;
+ num = *p++ - '0';
+ do {
+ num *= 10; num += *p++ - '0';
+ } while (p - tokStart < 8);
+ yylvalPtr->Number = num;
+ yyDigitCount = 8;
+ yyInput = p;
+ location->last_column = yyInput - info->dateStart - 1;
+ return tISOBASL;
+ }
+ if (yyDigitCount > 14) { /* overflow */
+ return tID;
+ }
+ if (yyDigitCount == 8) {
+ return tISOBAS8;
+ }
+ if (yyDigitCount == 6) {
+ return tISOBAS6;
+ }
}
+ /* ignore spaces after digits (optional) */
+ yyInput = bypassSpaces(yyInput);
+ return tUNUMBER;
}
if (!(c & 0x80) && isalpha(UCHAR(c))) { /* INTL: ISO only. */
+ int ret;
for (p = buff; isalpha(UCHAR(c = *yyInput++)) /* INTL: ISO only. */
|| c == '.'; ) {
- if (p < &buff[sizeof buff - 1]) {
+ if (p < &buff[sizeof(buff) - 1]) {
*p++ = c;
}
}
*p = '\0';
yyInput--;
location->last_column = yyInput - info->dateStart - 1;
- return LookupWord(yylvalPtr, buff);
+ ret = LookupWord(yylvalPtr, buff);
+ /*
+ * lookahead:
+ * for spaces to consider word boundaries (for instance
+ * literal T in isodateTisotimeZ is not a TZ, but Z is UTC);
+ * for +/- digit, to differentiate between "GMT+1000 day" and "GMT +1000 day";
+ * bypass spaces after token (but ignore by TZ+OFFS), because should
+ * recognize next SP token, if TZ only.
+ */
+ if (ret == tZONE || ret == tDAYZONE) {
+ c = *yyInput;
+ if (isdigit(UCHAR(c))) { /* literal not a TZ */
+ yyInput = tokStart;
+ return *yyInput++;
+ }
+ if ((c == '+' || c == '-') && isdigit(UCHAR(*(yyInput+1)))) {
+ if ( !isdigit(UCHAR(*(yyInput+2)))
+ || !isdigit(UCHAR(*(yyInput+3)))) {
+ /* GMT+1, GMT-10, etc. */
+ return tZONEwO2;
+ }
+ if ( isdigit(UCHAR(*(yyInput+4)))
+ && !isdigit(UCHAR(*(yyInput+5)))) {
+ /* GMT+1000, etc. */
+ return tZONEwO4;
+ }
+ }
+ }
+ yyInput = bypassSpaces(yyInput);
+ return ret;
+
}
if (c != '(') {
location->last_column = yyInput - info->dateStart;
@@ -958,169 +998,75 @@ TclDatelex(
} while (Count > 0);
}
}
-
+
int
-TclClockOldscanObjCmd(
- TCL_UNUSED(void *),
+TclClockFreeScan(
Tcl_Interp *interp, /* Tcl interpreter */
- int objc, /* Count of parameters */
- Tcl_Obj *const *objv) /* Parameters */
+ DateInfo *info) /* Input and result parameters */
{
- Tcl_Obj *result, *resultElement;
- int yr, mo, da;
- DateInfo dateInfo;
- DateInfo* info = &dateInfo;
int status;
- if (objc != 5) {
- Tcl_WrongNumArgs(interp, 1, objv,
- "stringToParse baseYear baseMonth baseDay" );
- return TCL_ERROR;
- }
+ #if YYDEBUG
+ /* enable debugging if compiled with YYDEBUG */
+ yydebug = 1;
+ #endif
- yyInput = TclGetString(objv[1]);
- dateInfo.dateStart = yyInput;
-
- yyHaveDate = 0;
- if (Tcl_GetIntFromObj(interp, objv[2], &yr) != TCL_OK
- || Tcl_GetIntFromObj(interp, objv[3], &mo) != TCL_OK
- || Tcl_GetIntFromObj(interp, objv[4], &da) != TCL_OK) {
- return TCL_ERROR;
- }
- yyYear = yr; yyMonth = mo; yyDay = da;
-
- yyHaveTime = 0;
- yyHour = 0; yyMinutes = 0; yySeconds = 0; yyMeridian = MER24;
-
- yyHaveZone = 0;
- yyTimezone = 0; yyDSTmode = DSTmaybe;
+ /*
+ * yyInput = stringToParse;
+ *
+ * ClockInitDateInfo(info) should be executed to pre-init info;
+ */
- yyHaveOrdinalMonth = 0;
- yyMonthOrdinal = 0;
+ yyDSTmode = DSTmaybe;
- yyHaveDay = 0;
- yyDayOrdinal = 0; yyDayNumber = 0;
+ info->separatrix = "";
- yyHaveRel = 0;
- yyRelMonth = 0; yyRelDay = 0; yyRelSeconds = 0; yyRelPointer = NULL;
+ info->dateStart = yyInput;
- TclNewObj(dateInfo.messages);
- dateInfo.separatrix = "";
- Tcl_IncrRefCount(dateInfo.messages);
+ /* ignore spaces at begin */
+ yyInput = bypassSpaces(yyInput);
- status = yyparse(&dateInfo);
+ /* parse */
+ status = yyparse(info);
if (status == 1) {
- Tcl_SetObjResult(interp, dateInfo.messages);
- Tcl_DecrRefCount(dateInfo.messages);
- Tcl_SetErrorCode(interp, "TCL", "VALUE", "DATE", "PARSE", (char *)NULL);
- return TCL_ERROR;
+ const char *msg = NULL;
+ if (info->errFlags & CLF_HAVEDATE) {
+ msg = "more than one date in string";
+ } else if (info->errFlags & CLF_TIME) {
+ msg = "more than one time of day in string";
+ } else if (info->errFlags & CLF_ZONE) {
+ msg = "more than one time zone in string";
+ } else if (info->errFlags & CLF_DAYOFWEEK) {
+ msg = "more than one weekday in string";
+ } else if (info->errFlags & CLF_ORDINALMONTH) {
+ msg = "more than one ordinal month in string";
+ }
+ if (msg) {
+ Tcl_SetObjResult(interp, Tcl_NewStringObj(msg, -1));
+ Tcl_SetErrorCode(interp, "TCL", "VALUE", "DATE", "MULTIPLE", (char *)NULL);
+ } else {
+ Tcl_SetObjResult(interp,
+ info->messages ? info->messages : Tcl_NewObj());
+ info->messages = NULL;
+ Tcl_SetErrorCode(interp, "TCL", "VALUE", "DATE", "PARSE", (char *)NULL);
+ }
+ status = TCL_ERROR;
} else if (status == 2) {
Tcl_SetObjResult(interp, Tcl_NewStringObj("memory exhausted", -1));
- Tcl_DecrRefCount(dateInfo.messages);
Tcl_SetErrorCode(interp, "TCL", "MEMORY", (char *)NULL);
- return TCL_ERROR;
+ status = TCL_ERROR;
} else if (status != 0) {
Tcl_SetObjResult(interp, Tcl_NewStringObj("Unknown status returned "
"from date parser. Please "
"report this error as a "
"bug in Tcl.", -1));
- Tcl_DecrRefCount(dateInfo.messages);
Tcl_SetErrorCode(interp, "TCL", "BUG", (char *)NULL);
- return TCL_ERROR;
+ status = TCL_ERROR;
}
- Tcl_DecrRefCount(dateInfo.messages);
-
- if (yyHaveDate > 1) {
- Tcl_SetObjResult(interp,
- Tcl_NewStringObj("more than one date in string", -1));
- Tcl_SetErrorCode(interp, "TCL", "VALUE", "DATE", "MULTIPLE", (char *)NULL);
- return TCL_ERROR;
- }
- if (yyHaveTime > 1) {
- Tcl_SetObjResult(interp,
- Tcl_NewStringObj("more than one time of day in string", -1));
- Tcl_SetErrorCode(interp, "TCL", "VALUE", "DATE", "MULTIPLE", (char *)NULL);
- return TCL_ERROR;
- }
- if (yyHaveZone > 1) {
- Tcl_SetObjResult(interp,
- Tcl_NewStringObj("more than one time zone in string", -1));
- Tcl_SetErrorCode(interp, "TCL", "VALUE", "DATE", "MULTIPLE", (char *)NULL);
- return TCL_ERROR;
- }
- if (yyHaveDay > 1) {
- Tcl_SetObjResult(interp,
- Tcl_NewStringObj("more than one weekday in string", -1));
- Tcl_SetErrorCode(interp, "TCL", "VALUE", "DATE", "MULTIPLE", (char *)NULL);
- return TCL_ERROR;
- }
- if (yyHaveOrdinalMonth > 1) {
- Tcl_SetObjResult(interp,
- Tcl_NewStringObj("more than one ordinal month in string", -1));
- Tcl_SetErrorCode(interp, "TCL", "VALUE", "DATE", "MULTIPLE", (char *)NULL);
- return TCL_ERROR;
- }
-
- TclNewObj(result);
- TclNewObj(resultElement);
- if (yyHaveDate) {
- Tcl_ListObjAppendElement(interp, resultElement,
- Tcl_NewIntObj(yyYear));
- Tcl_ListObjAppendElement(interp, resultElement,
- Tcl_NewIntObj(yyMonth));
- Tcl_ListObjAppendElement(interp, resultElement,
- Tcl_NewIntObj(yyDay));
- }
- Tcl_ListObjAppendElement(interp, result, resultElement);
-
- if (yyHaveTime) {
- Tcl_ListObjAppendElement(interp, result, Tcl_NewIntObj(
- ToSeconds(yyHour, yyMinutes, yySeconds, (MERIDIAN)yyMeridian)));
- } else {
- TclNewObj(resultElement);
- Tcl_ListObjAppendElement(interp, result, resultElement);
- }
-
- TclNewObj(resultElement);
- if (yyHaveZone) {
- Tcl_ListObjAppendElement(interp, resultElement,
- Tcl_NewIntObj(-yyTimezone));
- Tcl_ListObjAppendElement(interp, resultElement,
- Tcl_NewIntObj(1 - yyDSTmode));
+ if (info->messages) {
+ Tcl_DecrRefCount(info->messages);
}
- Tcl_ListObjAppendElement(interp, result, resultElement);
-
- TclNewObj(resultElement);
- if (yyHaveRel) {
- Tcl_ListObjAppendElement(interp, resultElement,
- Tcl_NewIntObj(yyRelMonth));
- Tcl_ListObjAppendElement(interp, resultElement,
- Tcl_NewIntObj(yyRelDay));
- Tcl_ListObjAppendElement(interp, resultElement,
- Tcl_NewIntObj(yyRelSeconds));
- }
- Tcl_ListObjAppendElement(interp, result, resultElement);
-
- TclNewObj(resultElement);
- if (yyHaveDay && !yyHaveDate) {
- Tcl_ListObjAppendElement(interp, resultElement,
- Tcl_NewIntObj(yyDayOrdinal));
- Tcl_ListObjAppendElement(interp, resultElement,
- Tcl_NewIntObj(yyDayNumber));
- }
- Tcl_ListObjAppendElement(interp, result, resultElement);
-
- TclNewObj(resultElement);
- if (yyHaveOrdinalMonth) {
- Tcl_ListObjAppendElement(interp, resultElement,
- Tcl_NewIntObj(yyMonthOrdinal));
- Tcl_ListObjAppendElement(interp, resultElement,
- Tcl_NewIntObj(yyMonth));
- }
- Tcl_ListObjAppendElement(interp, result, resultElement);
-
- Tcl_SetObjResult(interp, result);
- return TCL_OK;
+ return status;
}
/*
diff --git a/generic/tclInt.h b/generic/tclInt.h
index ada17b3..3241651 100644
--- a/generic/tclInt.h
+++ b/generic/tclInt.h
@@ -3288,6 +3288,7 @@ MODULE_SCOPE int TclFindDictElement(Tcl_Interp *interp,
const char *dict, Tcl_Size dictLength,
const char **elementPtr, const char **nextPtr,
Tcl_Size *sizePtr, int *literalPtr);
+MODULE_SCOPE Tcl_Obj * TclDictObjSmartRef(Tcl_Interp *interp, Tcl_Obj *);
/* TIP #280 - Modified token based evaluation, with line information. */
MODULE_SCOPE int TclEvalEx(Tcl_Interp *interp, const char *script,
Tcl_Size numBytes, int flags, Tcl_Size line,
diff --git a/generic/tclStrIdxTree.c b/generic/tclStrIdxTree.c
new file mode 100644
index 0000000..bdb16f2
--- /dev/null
+++ b/generic/tclStrIdxTree.c
@@ -0,0 +1,530 @@
+/*
+ * tclStrIdxTree.c --
+ *
+ * Contains the routines for managing string index tries in Tcl.
+ *
+ * This code is back-ported from the tclSE engine, by Serg G. Brester.
+ *
+ * Copyright (c) 2016 by Sergey G. Brester aka sebres. All rights reserved.
+ *
+ * See the file "license.terms" for information on usage and redistribution of
+ * this file, and for a DISCLAIMER OF ALL WARRANTIES.
+ *
+ * -----------------------------------------------------------------------
+ *
+ * String index tries are prepaired structures used for fast greedy search of the string
+ * (index) by unique string prefix as key.
+ *
+ * Index tree build for two lists together can be explained in the following datagram
+ *
+ * Lists:
+ *
+ * {Januar Februar Maerz April Mai Juni Juli August September Oktober November Dezember}
+ * {Jnr Fbr Mrz Apr Mai Jni Jli Agt Spt Okt Nvb Dzb}
+ *
+ * Index-Tree:
+ *
+ * j 0 * ...
+ * anuar 1 *
+ * u 0 * a 0
+ * ni 6 * pril 4
+ * li 7 * ugust 8
+ * n 0 * gt 8
+ * r 1 * s 9
+ * i 6 * eptember 9
+ * li 7 * pt 9
+ * f 2 * oktober 10
+ * ebruar 2 * n 11
+ * br 2 * ovember 11
+ * m 0 * vb 11
+ * a 0 * d 12
+ * erz 3 * ezember 12
+ * i 5 * zb 12
+ * rz 3 *
+ * ...
+ *
+ * Thereby value 0 shows pure group items (corresponding ambigous matches).
+ * But the group may have a value if it contains only same values
+ * (see for example group "f" above).
+ *
+ * StrIdxTree's are very fast, so:
+ * build of above-mentioned tree takes about 10 microseconds.
+ * search of string index in this tree takes fewer as 0.1 microseconds.
+ *
+ */
+
+#include "tclInt.h"
+#include "tclStrIdxTree.h"
+
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TclStrIdxTreeSearch --
+ *
+ * Find largest part of string "start" in indexed tree (case sensitive).
+ *
+ * Also used for building of string index tree.
+ *
+ * Results:
+ * Return position of UTF character in start after last equal character
+ * and found item (with parent).
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+const char*
+TclStrIdxTreeSearch(
+ TclStrIdxTree **foundParent, /* Return value of found sub tree (used for tree build) */
+ TclStrIdx **foundItem, /* Return value of found item */
+ TclStrIdxTree *tree, /* Index tree will be browsed */
+ const char *start, /* UTF string to find in tree */
+ const char *end) /* End of string */
+{
+ TclStrIdxTree *parent = tree, *prevParent = tree;
+ TclStrIdx *item = tree->firstPtr, *prevItem = NULL;
+ const char *s = start, *f, *cin, *cinf, *prevf = NULL;
+ int offs = 0;
+
+ if (item == NULL) {
+ goto done;
+ }
+
+ /* search in tree */
+ do {
+ cinf = cin = TclGetString(item->key) + offs;
+ f = TclUtfFindEqualNCInLwr(s, end, cin, cin + item->length - offs, &cinf);
+ /* if something was found */
+ if (f > s) {
+ /* if whole string was found */
+ if (f >= end) {
+ start = f;
+ goto done;
+ };
+ /* set new offset and shift start string */
+ offs += cinf - cin;
+ s = f;
+ /* if match item, go deeper as long as possible */
+ if (offs >= item->length && item->childTree.firstPtr) {
+ /* save previuosly found item (if not ambigous) for
+ * possible fallback (few greedy match) */
+ if (item->value != NULL) {
+ prevf = f;
+ prevItem = item;
+ prevParent = parent;
+ }
+ parent = &item->childTree;
+ item = item->childTree.firstPtr;
+ continue;
+ }
+ /* no children - return this item and current chars found */
+ start = f;
+ goto done;
+ }
+
+ item = item->nextPtr;
+
+ } while (item != NULL);
+
+ /* fallback (few greedy match) not ambigous (has a value) */
+ if (prevItem != NULL) {
+ item = prevItem;
+ parent = prevParent;
+ start = prevf;
+ }
+
+done:
+
+ if (foundParent)
+ *foundParent = parent;
+ if (foundItem)
+ *foundItem = item;
+ return start;
+}
+
+void
+TclStrIdxTreeFree(
+ TclStrIdx *tree)
+{
+ while (tree != NULL) {
+ TclStrIdx *t;
+ Tcl_DecrRefCount(tree->key);
+ if (tree->childTree.firstPtr != NULL) {
+ TclStrIdxTreeFree(tree->childTree.firstPtr);
+ }
+ t = tree, tree = tree->nextPtr;
+ Tcl_Free(t);
+ }
+}
+
+/*
+ * Several bidirectional list primitives
+ */
+static inline void
+TclStrIdxTreeInsertBranch(
+ TclStrIdxTree *parent,
+ TclStrIdx *item,
+ TclStrIdx *child)
+{
+ if (parent->firstPtr == child)
+ parent->firstPtr = item;
+ if (parent->lastPtr == child)
+ parent->lastPtr = item;
+ if ( (item->nextPtr = child->nextPtr) ) {
+ item->nextPtr->prevPtr = item;
+ child->nextPtr = NULL;
+ }
+ if ( (item->prevPtr = child->prevPtr) ) {
+ item->prevPtr->nextPtr = item;
+ child->prevPtr = NULL;
+ }
+ item->childTree.firstPtr = child;
+ item->childTree.lastPtr = child;
+}
+
+static inline void
+TclStrIdxTreeAppend(
+ TclStrIdxTree *parent,
+ TclStrIdx *item)
+{
+ if (parent->lastPtr != NULL) {
+ parent->lastPtr->nextPtr = item;
+ }
+ item->prevPtr = parent->lastPtr;
+ item->nextPtr = NULL;
+ parent->lastPtr = item;
+ if (parent->firstPtr == NULL) {
+ parent->firstPtr = item;
+ }
+}
+
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TclStrIdxTreeBuildFromList --
+ *
+ * Build or extend string indexed tree from tcl list.
+ * If the values not given the values of built list are indices starts with 1.
+ * Value of 0 is thereby reserved to the ambigous values.
+ *
+ * Important: by multiple lists, optimal tree can be created only if list with
+ * larger strings used firstly.
+ *
+ * Results:
+ * Returns a standard Tcl result.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+int
+TclStrIdxTreeBuildFromList(
+ TclStrIdxTree *idxTree,
+ Tcl_Size lstc,
+ Tcl_Obj **lstv,
+ void **values)
+{
+ Tcl_Obj **lwrv;
+ Tcl_Size i;
+ int ret = TCL_ERROR;
+ void *val;
+ const char *s, *e, *f;
+ TclStrIdx *item;
+
+ /* create lowercase reflection of the list keys */
+
+ lwrv = (Tcl_Obj **)Tcl_Alloc(sizeof(Tcl_Obj*) * lstc);
+ if (lwrv == NULL) {
+ return TCL_ERROR;
+ }
+ for (i = 0; i < lstc; i++) {
+ lwrv[i] = Tcl_DuplicateObj(lstv[i]);
+ if (lwrv[i] == NULL) {
+ return TCL_ERROR;
+ }
+ Tcl_IncrRefCount(lwrv[i]);
+ lwrv[i]->length = Tcl_UtfToLower(TclGetString(lwrv[i]));
+ }
+
+ /* build index tree of the list keys */
+ for (i = 0; i < lstc; i++) {
+ TclStrIdxTree *foundParent = idxTree;
+ e = s = TclGetString(lwrv[i]);
+ e += lwrv[i]->length;
+ val = values ? values[i] : INT2PTR(i+1);
+
+ /* ignore empty keys (impossible to index it) */
+ if (lwrv[i]->length == 0) continue;
+
+ item = NULL;
+ if (idxTree->firstPtr != NULL) {
+ TclStrIdx *foundItem;
+ f = TclStrIdxTreeSearch(&foundParent, &foundItem,
+ idxTree, s, e);
+ /* if common prefix was found */
+ if (f > s) {
+ /* ignore element if fulfilled or ambigous */
+ if (f == e) {
+ continue;
+ }
+ /* if shortest key was found with the same value,
+ * just replace its current key with longest key */
+ if ( foundItem->value == val
+ && foundItem->length <= lwrv[i]->length
+ && foundItem->length <= (f - s) /* only if found item is covered in full */
+ && foundItem->childTree.firstPtr == NULL
+ ) {
+ Tcl_SetObjRef(foundItem->key, lwrv[i]);
+ foundItem->length = lwrv[i]->length;
+ continue;
+ }
+ /* split tree (e. g. j->(jan,jun) + jul == j->(jan,ju->(jun,jul)) )
+ * but don't split by fulfilled child of found item ( ii->iii->iiii ) */
+ if (foundItem->length != (f - s)) {
+ /* first split found item (insert one between parent and found + new one) */
+ item = (TclStrIdx *)Tcl_Alloc(sizeof(TclStrIdx));
+ if (item == NULL) {
+ goto done;
+ }
+ Tcl_InitObjRef(item->key, foundItem->key);
+ item->length = f - s;
+ /* set value or mark as ambigous if not the same value of both */
+ item->value = (foundItem->value == val) ? val : NULL;
+ /* insert group item between foundParent and foundItem */
+ TclStrIdxTreeInsertBranch(foundParent, item, foundItem);
+ foundParent = &item->childTree;
+ } else {
+ /* the new item should be added as child of found item */
+ foundParent = &foundItem->childTree;
+ }
+ }
+ }
+ /* append item at end of found parent */
+ item = (TclStrIdx *)Tcl_Alloc(sizeof(TclStrIdx));
+ if (item == NULL) {
+ goto done;
+ }
+ item->childTree.lastPtr = item->childTree.firstPtr = NULL;
+ Tcl_InitObjRef(item->key, lwrv[i]);
+ item->length = lwrv[i]->length;
+ item->value = val;
+ TclStrIdxTreeAppend(foundParent, item);
+ };
+
+ ret = TCL_OK;
+
+done:
+
+ if (lwrv != NULL) {
+ for (i = 0; i < lstc; i++) {
+ Tcl_DecrRefCount(lwrv[i]);
+ }
+ Tcl_Free(lwrv);
+ }
+
+ if (ret != TCL_OK) {
+ if (idxTree->firstPtr != NULL) {
+ TclStrIdxTreeFree(idxTree->firstPtr);
+ }
+ }
+
+ return ret;
+}
+
+
+static void
+StrIdxTreeObj_DupIntRepProc(Tcl_Obj *srcPtr, Tcl_Obj *copyPtr);
+static void
+StrIdxTreeObj_FreeIntRepProc(Tcl_Obj *objPtr);
+static void
+StrIdxTreeObj_UpdateStringProc(Tcl_Obj *objPtr);
+
+Tcl_ObjType StrIdxTreeObjType = {
+ "str-idx-tree", /* name */
+ StrIdxTreeObj_FreeIntRepProc, /* freeIntRepProc */
+ StrIdxTreeObj_DupIntRepProc, /* dupIntRepProc */
+ StrIdxTreeObj_UpdateStringProc, /* updateStringProc */
+ NULL, /* setFromAnyProc */
+ TCL_OBJTYPE_V0
+};
+
+Tcl_Obj*
+TclStrIdxTreeNewObj()
+{
+ Tcl_Obj *objPtr = Tcl_NewObj();
+ objPtr->internalRep.twoPtrValue.ptr1 = NULL;
+ objPtr->internalRep.twoPtrValue.ptr2 = NULL;
+ objPtr->typePtr = &StrIdxTreeObjType;
+ /* return tree root in internal representation */
+ return objPtr;
+}
+
+static void
+StrIdxTreeObj_DupIntRepProc(Tcl_Obj *srcPtr, Tcl_Obj *copyPtr)
+{
+ /* follow links (smart pointers) */
+ if ( srcPtr->internalRep.twoPtrValue.ptr1 != NULL
+ && srcPtr->internalRep.twoPtrValue.ptr2 == NULL
+ ) {
+ srcPtr = (Tcl_Obj*)srcPtr->internalRep.twoPtrValue.ptr1;
+ }
+ /* create smart pointer to it (ptr1 != NULL, ptr2 = NULL) */
+ Tcl_InitObjRef(*((Tcl_Obj **)&copyPtr->internalRep.twoPtrValue.ptr1),
+ srcPtr);
+ copyPtr->internalRep.twoPtrValue.ptr2 = NULL;
+ copyPtr->typePtr = &StrIdxTreeObjType;
+}
+
+static void
+StrIdxTreeObj_FreeIntRepProc(Tcl_Obj *objPtr)
+{
+ /* follow links (smart pointers) */
+ if ( objPtr->internalRep.twoPtrValue.ptr1 != NULL
+ && objPtr->internalRep.twoPtrValue.ptr2 == NULL
+ ) {
+ /* is a link */
+ Tcl_UnsetObjRef(*((Tcl_Obj **)&objPtr->internalRep.twoPtrValue.ptr1));
+ } else {
+ /* is a tree */
+ TclStrIdxTree *tree = (TclStrIdxTree*)&objPtr->internalRep.twoPtrValue.ptr1;
+ if (tree->firstPtr != NULL) {
+ TclStrIdxTreeFree(tree->firstPtr);
+ }
+ objPtr->internalRep.twoPtrValue.ptr1 = NULL;
+ objPtr->internalRep.twoPtrValue.ptr2 = NULL;
+ }
+ objPtr->typePtr = NULL;
+};
+
+static void
+StrIdxTreeObj_UpdateStringProc(Tcl_Obj *objPtr)
+{
+ /* currently only dummy empty string possible */
+ objPtr->length = 0;
+ objPtr->bytes = &tclEmptyString;
+};
+
+TclStrIdxTree *
+TclStrIdxTreeGetFromObj(Tcl_Obj *objPtr) {
+ /* follow links (smart pointers) */
+ if (objPtr->typePtr != &StrIdxTreeObjType) {
+ return NULL;
+ }
+ if ( objPtr->internalRep.twoPtrValue.ptr1 != NULL
+ && objPtr->internalRep.twoPtrValue.ptr2 == NULL
+ ) {
+ objPtr = (Tcl_Obj*)objPtr->internalRep.twoPtrValue.ptr1;
+ }
+ /* return tree root in internal representation */
+ return (TclStrIdxTree*)&objPtr->internalRep.twoPtrValue.ptr1;
+}
+
+/*
+ * Several debug primitives
+ */
+#if 0
+/* currently unused, debug resp. test purposes only */
+
+void
+TclStrIdxTreePrint(
+ Tcl_Interp *interp,
+ TclStrIdx *tree,
+ int offs)
+{
+ Tcl_Obj *obj[2];
+ const char *s;
+ Tcl_InitObjRef(obj[0], Tcl_NewStringObj("::puts", -1));
+ while (tree != NULL) {
+ s = TclGetString(tree->key) + offs;
+ Tcl_InitObjRef(obj[1], Tcl_ObjPrintf("%*s%.*s\t:%d",
+ offs, "", tree->length - offs, s, tree->value));
+ Tcl_PutsObjCmd(NULL, interp, 2, obj);
+ Tcl_UnsetObjRef(obj[1]);
+ if (tree->childTree.firstPtr != NULL) {
+ TclStrIdxTreePrint(interp, tree->childTree.firstPtr, tree->length);
+ }
+ tree = tree->nextPtr;
+ }
+ Tcl_UnsetObjRef(obj[0]);
+}
+
+
+int
+TclStrIdxTreeTestObjCmd(
+ ClientData clientData, Tcl_Interp *interp,
+ int objc, Tcl_Obj *const objv[])
+{
+ const char *cs, *cin, *ret;
+
+ static const char *const options[] = {
+ "index", "puts-index", "findequal",
+ NULL
+ };
+ enum optionInd {
+ O_INDEX, O_PUTS_INDEX, O_FINDEQUAL
+ };
+ int optionIndex;
+
+ if (objc < 2) {
+ Tcl_WrongNumArgs(interp, 1, objv, "");
+ return TCL_ERROR;
+ }
+ if (Tcl_GetIndexFromObj(interp, objv[1], options,
+ "option", 0, &optionIndex) != TCL_OK) {
+ Tcl_SetErrorCode(interp, "CLOCK", "badOption",
+ Tcl_GetString(objv[1]), (char *)NULL);
+ return TCL_ERROR;
+ }
+ switch (optionIndex) {
+ case O_FINDEQUAL:
+ if (objc < 4) {
+ Tcl_WrongNumArgs(interp, 1, objv, "");
+ return TCL_ERROR;
+ }
+ cs = TclGetString(objv[2]);
+ cin = TclGetString(objv[3]);
+ ret = TclUtfFindEqual(
+ cs, cs + objv[1]->length, cin, cin + objv[2]->length);
+ Tcl_SetObjResult(interp, Tcl_NewIntObj(ret - cs));
+ break;
+ case O_INDEX:
+ case O_PUTS_INDEX:
+
+ if (1) {
+ Tcl_Obj **lstv;
+ int i, lstc;
+ TclStrIdxTree idxTree = {NULL, NULL};
+ i = 1;
+ while (++i < objc) {
+ if (TclListObjGetElements(interp, objv[i],
+ &lstc, &lstv) != TCL_OK) {
+ return TCL_ERROR;
+ };
+ TclStrIdxTreeBuildFromList(&idxTree, lstc, lstv, NULL);
+ }
+ if (optionIndex == O_PUTS_INDEX) {
+ TclStrIdxTreePrint(interp, idxTree.firstPtr, 0);
+ }
+ TclStrIdxTreeFree(idxTree.firstPtr);
+ }
+ break;
+ }
+
+ return TCL_OK;
+}
+
+#endif
+
+/*
+ * Local Variables:
+ * mode: c
+ * c-basic-offset: 4
+ * fill-column: 78
+ * End:
+ */
diff --git a/generic/tclStrIdxTree.h b/generic/tclStrIdxTree.h
new file mode 100644
index 0000000..19e7624
--- /dev/null
+++ b/generic/tclStrIdxTree.h
@@ -0,0 +1,155 @@
+/*
+ * tclStrIdxTree.h --
+ *
+ * Declarations of string index tries and other primitives currently
+ * back-ported from tclSE.
+ *
+ * Copyright (c) 2016 Serg G. Brester (aka sebres)
+ *
+ * See the file "license.terms" for information on usage and redistribution
+ * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
+ */
+
+#ifndef _TCLSTRIDXTREE_H
+#define _TCLSTRIDXTREE_H
+
+
+/*
+ * Main structures declarations of index tree and entry
+ */
+
+typedef struct TclStrIdxTree {
+ struct TclStrIdx *firstPtr;
+ struct TclStrIdx *lastPtr;
+} TclStrIdxTree;
+
+typedef struct TclStrIdx {
+ struct TclStrIdxTree childTree;
+ struct TclStrIdx *nextPtr;
+ struct TclStrIdx *prevPtr;
+ Tcl_Obj *key;
+ int length;
+ void *value;
+} TclStrIdx;
+
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TclUtfFindEqual, TclUtfFindEqualNC --
+ *
+ * Find largest part of string cs in string cin (case sensitive and not).
+ *
+ * Results:
+ * Return position of UTF character in cs after last equal character.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static inline const char *
+TclUtfFindEqual(
+ const char *cs, /* UTF string to find in cin. */
+ const char *cse, /* End of cs */
+ const char *cin, /* UTF string will be browsed. */
+ const char *cine) /* End of cin */
+{
+ const char *ret = cs;
+ Tcl_UniChar ch1, ch2;
+ do {
+ cs += TclUtfToUniChar(cs, &ch1);
+ cin += TclUtfToUniChar(cin, &ch2);
+ if (ch1 != ch2) break;
+ } while ((ret = cs) < cse && cin < cine);
+ return ret;
+}
+
+static inline const char *
+TclUtfFindEqualNC(
+ const char *cs, /* UTF string to find in cin. */
+ const char *cse, /* End of cs */
+ const char *cin, /* UTF string will be browsed. */
+ const char *cine, /* End of cin */
+ const char **cinfnd) /* Return position in cin */
+{
+ const char *ret = cs;
+ Tcl_UniChar ch1, ch2;
+ do {
+ cs += TclUtfToUniChar(cs, &ch1);
+ cin += TclUtfToUniChar(cin, &ch2);
+ if (ch1 != ch2) {
+ ch1 = Tcl_UniCharToLower(ch1);
+ ch2 = Tcl_UniCharToLower(ch2);
+ if (ch1 != ch2) break;
+ }
+ *cinfnd = cin;
+ } while ((ret = cs) < cse && cin < cine);
+ return ret;
+}
+
+static inline const char *
+TclUtfFindEqualNCInLwr(
+ const char *cs, /* UTF string (in anycase) to find in cin. */
+ const char *cse, /* End of cs */
+ const char *cin, /* UTF string (in lowercase) will be browsed. */
+ const char *cine, /* End of cin */
+ const char **cinfnd) /* Return position in cin */
+{
+ const char *ret = cs;
+ Tcl_UniChar ch1, ch2;
+ do {
+ cs += TclUtfToUniChar(cs, &ch1);
+ cin += TclUtfToUniChar(cin, &ch2);
+ if (ch1 != ch2) {
+ ch1 = Tcl_UniCharToLower(ch1);
+ if (ch1 != ch2) break;
+ }
+ *cinfnd = cin;
+ } while ((ret = cs) < cse && cin < cine);
+ return ret;
+}
+
+/*
+ * Primitives to safe set, reset and free references.
+ */
+
+#define Tcl_UnsetObjRef(obj) \
+ if (obj != NULL) { Tcl_DecrRefCount(obj); obj = NULL; }
+#define Tcl_InitObjRef(obj, val) \
+ obj = val; if (obj) { Tcl_IncrRefCount(obj); }
+#define Tcl_SetObjRef(obj, val) \
+if (1) { \
+ Tcl_Obj *nval = val; \
+ if (obj != nval) { \
+ Tcl_Obj *prev = obj; \
+ Tcl_InitObjRef(obj, nval); \
+ if (prev != NULL) { Tcl_DecrRefCount(prev); }; \
+ } \
+}
+
+/*
+ * Prototypes of module functions.
+ */
+
+MODULE_SCOPE const char*
+ TclStrIdxTreeSearch(TclStrIdxTree **foundParent,
+ TclStrIdx **foundItem, TclStrIdxTree *tree,
+ const char *start, const char *end);
+
+MODULE_SCOPE int TclStrIdxTreeBuildFromList(TclStrIdxTree *idxTree,
+ Tcl_Size lstc, Tcl_Obj **lstv, void **values);
+
+MODULE_SCOPE Tcl_Obj*
+ TclStrIdxTreeNewObj();
+
+MODULE_SCOPE TclStrIdxTree*
+ TclStrIdxTreeGetFromObj(Tcl_Obj *objPtr);
+
+#if 1
+
+MODULE_SCOPE Tcl_ObjCmdProc TclStrIdxTreeTestObjCmd;
+#endif
+
+#endif /* _TCLSTRIDXTREE_H */