diff options
author | jan.nijtmans <nijtmans@users.sourceforge.net> | 2024-03-20 08:24:17 (GMT) |
---|---|---|
committer | jan.nijtmans <nijtmans@users.sourceforge.net> | 2024-03-20 08:24:17 (GMT) |
commit | 72e5911a9e1eed937ac5c5861b546f4803c03d1e (patch) | |
tree | 8f47867af5163c86b4bf6fbc6a0a15ed1e06d4a3 /generic | |
parent | a72c1f969cba896ca61001393f0f7b74fb52e194 (diff) | |
parent | ba192358693ff4446b9cbd8fda91fb0958ea07e5 (diff) | |
download | tcl-72e5911a9e1eed937ac5c5861b546f4803c03d1e.zip tcl-72e5911a9e1eed937ac5c5861b546f4803c03d1e.tar.gz tcl-72e5911a9e1eed937ac5c5861b546f4803c03d1e.tar.bz2 |
Rebase to latest 9.0
Diffstat (limited to 'generic')
-rw-r--r-- | generic/tclClock.c | 3227 | ||||
-rw-r--r-- | generic/tclClockFmt.c | 3391 | ||||
-rw-r--r-- | generic/tclDate.c | 1044 | ||||
-rw-r--r-- | generic/tclDate.h | 569 | ||||
-rw-r--r-- | generic/tclDictObj.c | 54 | ||||
-rw-r--r-- | generic/tclGetDate.y | 682 | ||||
-rw-r--r-- | generic/tclInt.h | 1 | ||||
-rw-r--r-- | generic/tclStrIdxTree.c | 530 | ||||
-rw-r--r-- | generic/tclStrIdxTree.h | 155 |
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 **)©Ptr->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 */ |