diff options
-rw-r--r-- | doc/clock.n | 70 | ||||
-rw-r--r-- | generic/tclClock.c | 3226 | ||||
-rw-r--r-- | generic/tclClockFmt.c | 3463 | ||||
-rw-r--r-- | generic/tclDate.c | 1046 | ||||
-rw-r--r-- | generic/tclDate.h | 575 | ||||
-rw-r--r-- | generic/tclDictObj.c | 54 | ||||
-rw-r--r-- | generic/tclGetDate.y | 684 | ||||
-rw-r--r-- | generic/tclInt.h | 1 | ||||
-rw-r--r-- | generic/tclStrIdxTree.c | 528 | ||||
-rw-r--r-- | generic/tclStrIdxTree.h | 167 | ||||
-rw-r--r-- | library/clock.tcl | 2953 | ||||
-rw-r--r-- | library/init.tcl | 23 | ||||
-rw-r--r-- | library/tclIndex | 23 | ||||
-rw-r--r-- | tests-perf/clock.perf.tcl | 10 | ||||
-rw-r--r-- | tests/clock-ivm.test | 8 | ||||
-rw-r--r-- | tests/clock.test | 1499 | ||||
-rw-r--r-- | unix/Makefile.in | 17 | ||||
-rw-r--r-- | win/Makefile.in | 2 | ||||
-rw-r--r-- | win/makefile.vc | 2 |
19 files changed, 10275 insertions, 4076 deletions
diff --git a/doc/clock.n b/doc/clock.n index e1253aa..5a6e3e4 100644 --- a/doc/clock.n +++ b/doc/clock.n @@ -101,6 +101,15 @@ slowing its clock by a tiny fraction for some minutes until it is back in sync with UTC; its data model does not represent minutes that have 59 or 61 seconds. .TP +\fI\-now\fR +Instead of \fItimeVal\fR a non-integer option \fI\-now\fR can be used as +replacement for today, which is simply interpolated to the runt-time as value +of \fBclock seconds\fR. For example: +.sp +\fBclock format -now -f %a; # current day of the week\fR +.sp +\fBclock add -now 1 month; # next month\fR +.TP \fIunit\fR . One of the words, \fBseconds\fR, \fBminutes\fR, \fBhours\fR, @@ -413,8 +422,12 @@ preprocessed format string. In order of preference: If the string contains a \fB%s\fR format group, representing seconds from the epoch, that group is used to determine the date. .IP [2] -If the string contains a \fB%J\fR format group, representing -the Julian Day Number, that group is used to determine the date. +If the string contains a \fB%J\fR, \fB%EJ\fR or \fB%Ej\fR format groups, +representing the Calendar or Astronomical Julian Day Number, that groups +are used to determine the date. +Note, that in case of \fB%EJ\fR or \fB%Ej\fR format groups, representing +the Julian Date with time fraction, this groups may be used to determine +the date and time. .IP [3] If the string contains a complete set of format groups specifying century, year, month, and day of month; century, year, and day of year; @@ -552,6 +565,33 @@ the string \fBB.C.E.\fR, \fBB.C.\fR, \fBC.E.\fR, \fBA.D.\fR, or the abbreviation appropriate to the current locale, and uses it to fix whether \fB%Y\fR refers to years before or after Year 1 of the Common Era. +.IP \fB%Ej\fR +On output, produces a string of digits giving the Astronomical Julian Date or +Astronomical Julian Day Number (JDN/JD). In opposite to calendar julian day +\fB%J\fR, it starts the day at noon. +On input, accepts a string of digits (or floating point with the time fraction) +and interprets it as an Astronomical Julian Day Number (JDN/JD). +The Astronomical Julian Date is a count of the number of calendar days +that have elapsed since 1 January, 4713 BCE of the proleptic +Julian calendar, which contains also the time fraktion (after floating point). +The epoch time of 1 January 1970 corresponds to Astronomical JDN 2440587.5. +This value corresponds the julian day used in sqlite-database, and is the same +as result of \fBselect julianday(:seconds, 'unixepoch')\fR. +.IP \fB%EJ\fR +On output, produces a string of digits giving the Calendar Julian Date. +In opposite to julian day \fB%J\fR format group, it produces float number. +In opposite to astronomical julian day \fB%Ej\fR group, it starts at midnight. +On input, accepts a string of digits (or floating point with the time fraction) +and interprets it as a Calendar Julian Day Number. +The Calendar Julian Date is a count of the number of calendar days +that have elapsed since 1 January, 4713 BCE of the proleptic +Julian calendar, which contains also the time fraktion (after floating point). +The epoch time of 1 January 1970 corresponds to Astronomical JDN 2440588. +.IP \fB%Es\fR +This affects similar to \fB%s\fR, but in opposition to \fB%s\fR it parses +or formats local seconds (not the posix seconds). +Because \fB%s\fR has the same precedence as \fB%s\fR (uniquely determines +a point in time), it overrides all other input formats. .IP \fB%Ex\fR On output, produces a locale-dependent representation of the date in the locale's alternative calendar. On input, matches @@ -591,7 +631,7 @@ On output, produces a two-digit number giving the hour of the day On output, produces a three-digit number giving the day of the year (001-366). On input, accepts such a number. .IP \fB%J\fR -On output, produces a string of digits giving the Julian Day Number. +On output, produces a string of digits giving the calendar Julian Day Number. On input, accepts a string of digits and interprets it as a Julian Day Number. The Julian Day Number is a count of the number of calendar days that have elapsed since 1 January, 4713 BCE of the proleptic @@ -712,12 +752,14 @@ week number \fB%V\fR; programs should use \fB%G\fR for that purpose. On output, produces the current time zone, expressed in hours and minutes east (+hhmm) or west (\-hhmm) of Greenwich. On input, accepts a time zone specifier (see \fBTIME ZONES\fR below) that will be used to -determine the time zone. +determine the time zone (this token is optionally applicable on input, +so the value is not mandatory and can be missing in input). .IP \fB%Z\fR On output, produces the current time zone's name, possibly translated to the given locale. On input, accepts a time zone specifier (see \fBTIME ZONES\fR below) that will be used to determine the -time zone. This option should, in general, be used on input only when +time zone (token is also like \fB%z\fR optionally applicable on input). +This option should, in general, be used on input only when parsing RFC822 dates. Other uses are fraught with ambiguity; for instance, the string \fBBST\fR may represent British Summer Time or Brazilian Standard Time. It is recommended that date/time strings for @@ -922,6 +964,24 @@ used. Finally, a correction is applied so that the correct hour of the day is produced after allowing for daylight savings time differences and the correct date is given when going from the end of a long month to a short month. +.PP +The precedence of the applying of single tokens resp. which sequence will be +used by calculating of the time is complex, e. g. heavily dependent on the +precision of type of the token. +.sp +In example below the second date-string contains "next January", therefore +it results in next year but in January. And third date-string besides "January" +contains also additionally "Fri", so it results in the nearest Friday. +Thus both win before "385 days" resp. make it more precise, because of higher +precision of this token types. +.CS +% clock format [clock scan "5 years 18 months 385 days" -base 0 -gmt 1] -gmt 1 +Thu Jul 21 00:00:00 GMT 1977 +% clock format [clock scan "5 years 18 months 385 days next January" -base 0 -gmt 1] -gmt 1 +Sat Jan 21 00:00:00 GMT 1978 +% clock format [clock scan "5 years 18 months 385 days next January Fri" -base 0 -gmt 1] -gmt 1 +Fri Jan 27 00:00:00 GMT 1978 +.CE .SH "SEE ALSO" msgcat(n) .SH KEYWORDS diff --git a/generic/tclClock.c b/generic/tclClock.c index b870f79..7c179f6 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,1196 @@ 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]); + TclInitObjRef(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, ClockDeleteCmdProc); + 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; + TclUnsetObjRef(data->systemTimeZone); + TclUnsetObjRef(data->systemSetupTZData); + TclUnsetObjRef(data->gmtSetupTimeZoneUnnorm); + TclUnsetObjRef(data->gmtSetupTimeZone); + TclUnsetObjRef(data->gmtSetupTZData); + TclUnsetObjRef(data->gmtTZName); + TclUnsetObjRef(data->lastSetupTimeZoneUnnorm); + TclUnsetObjRef(data->lastSetupTimeZone); + TclUnsetObjRef(data->lastSetupTZData); + TclUnsetObjRef(data->prevSetupTimeZoneUnnorm); + TclUnsetObjRef(data->prevSetupTimeZone); + TclUnsetObjRef(data->prevSetupTZData); + + TclUnsetObjRef(data->defaultLocale); + data->defaultLocaleDict = NULL; + TclUnsetObjRef(data->currentLocale); + data->currentLocaleDict = NULL; + TclUnsetObjRef(data->lastUsedLocaleUnnorm); + TclUnsetObjRef(data->lastUsedLocale); + data->lastUsedLocaleDict = NULL; + TclUnsetObjRef(data->prevUsedLocaleUnnorm); + TclUnsetObjRef(data->prevUsedLocale); + data->prevUsedLocaleDict = NULL; + + TclUnsetObjRef(data->lastBase.timezoneObj); + + TclUnsetObjRef(data->lastTZOffsCache[0].timezoneObj); + TclUnsetObjRef(data->lastTZOffsCache[0].tzName); + TclUnsetObjRef(data->lastTZOffsCache[1].timezoneObj); + TclUnsetObjRef(data->lastTZOffsCache[1].tzName); + + TclUnsetObjRef(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) { + TclSetObjRef(dataPtr->prevSetupTimeZoneUnnorm, dataPtr->lastSetupTimeZoneUnnorm); + TclSetObjRef(dataPtr->prevSetupTimeZone, timezoneObj); + TclSetObjRef(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 + ) { + TclSetObjRef(dataPtr->lastSetupTimeZoneUnnorm, timezoneObj); + return dataPtr->lastSetupTimeZone; + } + if (dataPtr->prevSetupTimeZone != NULL && + strcmp(tz, TclGetString(dataPtr->prevSetupTimeZone)) == 0 + ) { + TclSetObjRef(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) { + TclSetObjRef(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; + } + + TclSetObjRef(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) { + TclSetObjRef(dataPtr->prevUsedLocaleUnnorm, dataPtr->lastUsedLocaleUnnorm); + TclSetObjRef(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; + TclSetObjRef(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; + TclSetObjRef(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); + TclSetObjRef(dataPtr->lastUsedLocaleUnnorm, localeObj); + localeObj = ClockGetSystemLocale(dataPtr, interp); + TclSetObjRef(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) { + TclInitObjRef(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) { + TclSetObjRef(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); + TclSetObjRef(dataPtr->lastUsedLocale, opts->localeObj); + TclUnsetObjRef(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) { + TclInitObjRef(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) { + TclSetObjRef(dataPtr->gmtSetupTimeZone, + dataPtr->literals[LIT_GMT]); + } + TclSetObjRef(dataPtr->gmtSetupTimeZoneUnnorm, tzUnnormObj); + return; + } + + /* last setup zone loaded */ + if (dataPtr->lastSetupTimeZone != timezoneObj) { + SavePrevTimezoneObj(dataPtr); + TclSetObjRef(dataPtr->lastSetupTimeZone, timezoneObj); + TclUnsetObjRef(dataPtr->lastSetupTZData); + } + TclSetObjRef(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: { + /* validate current tz-epoch */ + size_t lastTZEpoch = TzsetIfNecessary(); + if (i < objc) { + if (dataPtr->systemTimeZone != objv[i]) { + TclSetObjRef(dataPtr->systemTimeZone, objv[i]); + TclUnsetObjRef(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]) { + TclSetObjRef(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]) { + TclSetObjRef(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) { + TclSetObjRef(*out, ret); + } + else + if (dataPtr->lastSetupTimeZone != timezoneObj) { + SavePrevTimezoneObj(dataPtr); + TclSetObjRef(dataPtr->lastSetupTimeZone, timezoneObj); + TclUnsetObjRef(dataPtr->lastSetupTimeZoneUnnorm); + TclSetObjRef(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; + } + + TclUnsetObjRef(dataPtr->systemTimeZone); + TclUnsetObjRef(dataPtr->systemSetupTZData); + + if (Tcl_EvalObjv(interp, 1, &dataPtr->literals[LIT_GETSYSTEMTIMEZONE], 0) != TCL_OK) { + return NULL; + } + if (dataPtr->systemTimeZone == NULL) { + TclSetObjRef(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 */ + TclSetObjRef(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 +1404,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 +1437,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 +1459,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 +1493,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 +1526,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 +1551,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 +1597,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 +1723,8 @@ ClockGetjuliandayfromerayearmonthdayObjCmd( int status; int isBce = 0; + fields.tzName = NULL; + /* * Check params. */ @@ -653,6 +1809,8 @@ ClockGetjuliandayfromerayearweekdayObjCmd( int status; int isBce = 0; + fields.tzName = NULL; + /* * Check params. */ @@ -720,18 +1878,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 +1945,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 */ + TclSetObjRef(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]; + TclUnsetObjRef(dataPtr->lastTZOffsCache[1].timezoneObj); + TclUnsetObjRef(dataPtr->lastTZOffsCache[1].tzName); + memcpy(&dataPtr->lastTZOffsCache[1], ltzoc, sizeof(*ltzoc)); + TclInitObjRef(ltzoc->timezoneObj, timezoneObj); + ltzoc->changeover = changeover; + TclInitObjRef(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 +2022,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 +2046,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 +2058,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; + TclSetObjRef(fields->tzName, have[i].tzName); + return TCL_OK; } @@ -850,19 +2106,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 +2172,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; + } + TclSetObjRef(dataPtr->gmtTZName, tzName); + } + TclSetObjRef(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; + TclSetObjRef(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 +2243,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 */ + TclSetObjRef(ltzoc->tzName, fields->tzName); + } else { + /* no TZ in cache - just move second slot down and use the first one */ + ltzoc = &dataPtr->lastTZOffsCache[0]; + TclUnsetObjRef(dataPtr->lastTZOffsCache[1].timezoneObj); + TclUnsetObjRef(dataPtr->lastTZOffsCache[1].tzName); + memcpy(&dataPtr->lastTZOffsCache[1], ltzoc, sizeof(*ltzoc)); + TclInitObjRef(ltzoc->timezoneObj, timezoneObj); + ltzoc->changeover = changeover; + TclInitObjRef(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 +2310,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 +2321,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 +2332,7 @@ ConvertUTCToLocalUsingTable( * Convert the time. */ - fields->tzName = cellv[3]; - Tcl_IncrRefCount(fields->tzName); + TclSetObjRef(fields->tzName, cellv[3]); fields->localSeconds = fields->seconds + fields->tzOffset; return TCL_OK; } @@ -1030,7 +2365,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 +2402,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 +2418,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); + TclSetObjRef(fields->tzName, Tcl_NewStringObj(buffer, p - buffer)); return TCL_OK; } @@ -1109,16 +2441,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 +2467,11 @@ LookupLastTransition( * anyway. */ - if (tick < compVal) { + if (tick < (fromVal = compVal)) { + if (rangesVal) { + rangesVal[0] = fromVal; + rangesVal[1] = toVal; + } return rowv[0]; } @@ -1153,10 +2490,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 +2531,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 +2755,7 @@ GetMonthDay( *---------------------------------------------------------------------- */ -static void +void GetJulianDayFromEraYearWeekDay( TclDateFields *fields, /* Date to convert */ int changeover) /* Julian Day Number of the Gregorian @@ -1419,6 +2765,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 +2808,7 @@ GetJulianDayFromEraYearWeekDay( *---------------------------------------------------------------------- */ -static void +void GetJulianDayFromEraYearMonthDay( TclDateFields *fields, /* Date to convert */ int changeover) /* Gregorian transition date as a Julian Day */ @@ -1557,6 +2905,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 +2971,7 @@ GetJulianDayFromEraYearMonthDay( *---------------------------------------------------------------------- */ -static int +int IsGregorianLeapYear( TclDateFields *fields) /* Date to test */ { @@ -1767,14 +3170,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 +3224,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 +3260,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); + TclSetObjRef(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: + + TclUnsetObjRef(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: + + TclUnsetObjRef(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; + } + + // TclSetObjRef(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: + + TclUnsetObjRef(yy.date.tzName); + + if (ret != TCL_OK) { + return ret; + } + + Tcl_SetObjResult(interp, Tcl_NewWideIntObj(yy.date.seconds)); + return TCL_OK; } /*---------------------------------------------------------------------- @@ -2011,7 +4580,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 +4593,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 */ + TclInitObjRef(statePtr->errorInfo, + Tcl_ObjGetVar2(interp, iPtr->eiVar, NULL, 0)); + flags |= ERR_LEGACY_COPY; + } + if (!statePtr->errorCode) { + /* todo: avoid traced get of errorCode here */ + TclInitObjRef(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 */ + TclSetObjRef(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 +4686,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 +4705,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 +4725,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..0a5a029 --- /dev/null +++ b/generic/tclClockFmt.c @@ -0,0 +1,3463 @@ +/* + * 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 + * + *---------------------------------------------------------------------- + */ + +static inline void +_str2int_no( + int *out, + const char *p, + const char *e, + int sign) +{ + /* assert(e <= p+10); */ + int val = 0; + /* overflow impossible for 10 digits ("9..9"), so no needs to check at all */ + while (p < e) { /* never overflows */ + val = val * 10 + (*p++ - '0'); + } + if (sign < 0) { val = -val; } + *out = val; +} + +static inline void +_str2wideInt_no( + Tcl_WideInt *out, + const char *p, + const char *e, + int sign) +{ + /* assert(e <= p+18); */ + Tcl_WideInt val = 0; + /* overflow impossible for 18 digits ("9..9"), so no needs to check at all */ + while (p < e) { /* never overflows */ + val = val * 10 + (*p++ - '0'); + } + if (sign < 0) { val = -val; } + *out = val; +} + +/* int & Tcl_WideInt 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; + /* overflow impossible for 10 digits ("9..9"), so no needs to check before */ + const char *eNO = p+10; + if (eNO > e) { + eNO = e; + } + while (p < eNO) { /* never overflows */ + val = val * 10 + (*p++ - '0'); + } + if (sign >= 0) { + while (p < e) { /* check for overflow */ + int prev = val; + val = val * 10 + (*p++ - '0'); + if (val / 10 < prev) { + return TCL_ERROR; + } + } + } else { + val = -val; + while (p < e) { /* check for overflow */ + int prev = val; + val = val * 10 - (*p++ - '0'); + if (val / 10 > prev) { + return TCL_ERROR; + } + } + } + *out = val; + return TCL_OK; +} + +static inline int +_str2wideInt( + Tcl_WideInt *out, + const char *p, + const char *e, + int sign) +{ + Tcl_WideInt val = 0; + /* overflow impossible for 18 digits ("9..9"), so no needs to check before */ + const char *eNO = p+18; + if (eNO > e) { + eNO = e; + } + while (p < eNO) { /* never overflows */ + val = val * 10 + (*p++ - '0'); + } + if (sign >= 0) { + while (p < e) { /* check for overflow */ + Tcl_WideInt prev = val; + val = val * 10 + (*p++ - '0'); + if (val / 10 < prev) { + return TCL_ERROR; + } + } + } else { + val = -val; + while (p < e) { /* check for overflow */ + Tcl_WideInt prev = val; + val = val * 10 - (*p++ - '0'); + if (val / 10 > prev) { + return TCL_ERROR; + } + } + } + *out = val; + return TCL_OK; +} + +int +TclAtoWIe( + Tcl_WideInt *out, + const char *p, + const char *e, + int sign) +{ + return _str2wideInt(out, p, e, sign); +} + +#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) { + TclInitObjRef(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) { + TclUnsetObjRef(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)); + TclInitObjRef(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) { + TclUnsetObjRef(ObjLocFmtKey(valObj)); + ObjLocFmtKey(valObj) = valObj; + } + } + } + +done: + + TclUnsetObjRef(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 (size <= 10) { + _str2int_no((int *)(((char *)info) + map->offs), + p, x, sign); + } else { + if (_str2int((int *)(((char *)info) + map->offs), + p, x, sign) != TCL_OK) { + goto overflow; + } + } + p = x; + } else { + if (size <= 18) { + _str2wideInt_no((Tcl_WideInt *)(((char *)info) + map->offs), + p, x, sign); + } 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..a22168f 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; + Tcl_WideInt 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 28: /* day: sign SP tUNUMBER tDAY */ + { + yyDayOrdinal = (yyvsp[-3].Number) * (yyvsp[-1].Number); + yyDayOfWeek = (yyvsp[0].Number); } break; - case 23: /* day: sign tUNUMBER tDAY */ + 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 */ + { + (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 (yyHaveTime && yyHaveDate && !yyHaveRel) { + 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,110 @@ 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 the number of digits. */ - - Count = 0; - for (yylvalPtr->Number = 0; - isdigit(UCHAR(c = *yyInput++)); ) { /* INTL: digit */ - yylvalPtr->Number = 10 * yylvalPtr->Number + c - '0'; - Count++; + p = (char *)yyInput; + while (isdigit(UCHAR(*++p))) {}; + yyDigitCount = p - yyInput; + /* + * A number with 12 or 14 digits is considered an ISO 8601 date. + */ + 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 *)yyInput+8; + if (TclAtoWIe(&yylvalPtr->Number, yyInput, p, 1) != TCL_OK) { + return tID; /* overflow*/ + } + yyDigitCount = 8; + yyInput = p; + location->last_column = yyInput - info->dateStart - 1; + return tISOBASL; } - yyInput--; - yyDigitCount = Count; - + /* + * Convert the string into a number + */ + if (TclAtoWIe(&yylvalPtr->Number, yyInput, p, 1) != TCL_OK) { + return tID; /* overflow*/ + } + 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 == 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 +2618,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; + #if YYDEBUG + /* enable debugging if compiled with YYDEBUG */ + yydebug = 1; + #endif - 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; - } - 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..cdef706 --- /dev/null +++ b/generic/tclDate.h @@ -0,0 +1,575 @@ +/* + * 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. + */ + +#ifndef TCL_MEM_DEBUG +# define CLOCK_FMT_SCN_STORAGE_GC_SIZE 32 +#else +# define CLOCK_FMT_SCN_STORAGE_GC_SIZE 0 +#endif + +#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) \ + do { \ + 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--; \ + } \ + } \ + } while(0) + +/* + * 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 int + TclAtoWIe(Tcl_WideInt *out, const char *p, const char *e, int sign); + +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 91a6b36..7128e9c 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..3b04de4 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; + Tcl_WideInt 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,110 @@ 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 the number of digits. */ - - Count = 0; - for (yylvalPtr->Number = 0; - isdigit(UCHAR(c = *yyInput++)); ) { /* INTL: digit */ - yylvalPtr->Number = 10 * yylvalPtr->Number + c - '0'; - Count++; + p = (char *)yyInput; + while (isdigit(UCHAR(*++p))) {}; + yyDigitCount = p - yyInput; + /* + * A number with 12 or 14 digits is considered an ISO 8601 date. + */ + 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 *)yyInput+8; + if (TclAtoWIe(&yylvalPtr->Number, yyInput, p, 1) != TCL_OK) { + return tID; /* overflow*/ + } + yyDigitCount = 8; + yyInput = p; + location->last_column = yyInput - info->dateStart - 1; + return tISOBASL; } - yyInput--; - yyDigitCount = Count; - + /* + * Convert the string into a number + */ + if (TclAtoWIe(&yylvalPtr->Number, yyInput, p, 1) != TCL_OK) { + return tID; /* overflow*/ + } + 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 == 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 +994,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; + #if YYDEBUG + /* enable debugging if compiled with YYDEBUG */ + yydebug = 1; + #endif - 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; - } - 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; + status = 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 (info->messages) { + Tcl_DecrRefCount(info->messages); } - 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)); - } - 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 50c7add..8ccad81 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..f764d02 --- /dev/null +++ b/generic/tclStrIdxTree.c @@ -0,0 +1,528 @@ +/* + * 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; + Tcl_Size 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; + ckfree(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 **)ckalloc(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 + ) { + TclSetObjRef(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 *)ckalloc(sizeof(TclStrIdx)); + if (item == NULL) { + goto done; + } + TclInitObjRef(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 *)ckalloc(sizeof(TclStrIdx)); + if (item == NULL) { + goto done; + } + item->childTree.lastPtr = item->childTree.firstPtr = NULL; + TclInitObjRef(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]); + } + ckfree(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) */ + TclInitObjRef(*((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 */ + TclUnsetObjRef(*((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 */ + +static void +TclStrIdxTreePrint( + Tcl_Interp *interp, + TclStrIdx *tree, + int offs) +{ + Tcl_Obj *obj[2]; + const char *s; + TclInitObjRef(obj[0], Tcl_NewStringObj("::puts", -1)); + while (tree != NULL) { + s = TclGetString(tree->key) + offs; + TclInitObjRef(obj[1], Tcl_ObjPrintf("%*s%.*s\t:%d", + offs, "", tree->length - offs, s, tree->value)); + Tcl_PutsObjCmd(NULL, interp, 2, obj); + TclUnsetObjRef(obj[1]); + if (tree->childTree.firstPtr != NULL) { + TclStrIdxTreePrint(interp, tree->childTree.firstPtr, tree->length); + } + tree = tree->nextPtr; + } + TclUnsetObjRef(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: { + 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..5052823 --- /dev/null +++ b/generic/tclStrIdxTree.h @@ -0,0 +1,167 @@ +/* + * 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; + Tcl_Size 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 TclUnsetObjRef(obj) \ + do { \ + if (obj != NULL) { \ + Tcl_DecrRefCount(obj); \ + obj = NULL; \ + } \ + } while (0) +#define TclInitObjRef(obj, val) \ + do { \ + obj = val; \ + if (obj) { \ + Tcl_IncrRefCount(obj); \ + } \ + } while (0) +#define TclSetObjRef(obj, val) \ + do { \ + Tcl_Obj *nval = val; \ + if (obj != nval) { \ + Tcl_Obj *prev = obj; \ + TclInitObjRef(obj, nval); \ + if (prev != NULL) { \ + Tcl_DecrRefCount(prev); \ + }; \ + } \ + } while (0) + +/* + * 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 0 +/* currently unused, debug resp. test purposes only */ +MODULE_SCOPE Tcl_ObjCmdProc TclStrIdxTreeTestObjCmd; +#endif + +#endif /* _TCLSTRIDXTREE_H */ diff --git a/library/clock.tcl b/library/clock.tcl index d80fb2f..ef82372 100644 --- a/library/clock.tcl +++ b/library/clock.tcl @@ -10,22 +10,15 @@ #---------------------------------------------------------------------- # # Copyright © 2004-2007 Kevin B. Kenny +# 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. # #---------------------------------------------------------------------- -# msgcat 1.7 features are used. We need access to the Registry on Windows -# systems. +# msgcat 1.7 features are used. -uplevel \#0 { - package require msgcat 1.7 - if { $::tcl_platform(platform) eq {windows} } { - if { [catch { package require registry 1.1 }] } { - namespace eval ::tcl::clock [list variable NoRegistry {}] - } - } -} +package require msgcat 1.7 # Put the library directory into the namespace for the ensemble so that the # library code can find message catalogs and time zone definition files. @@ -58,9 +51,7 @@ namespace eval ::tcl::clock { # Import the message catalog commands that we use. - namespace import ::msgcat::mcload namespace import ::msgcat::mclocale - proc mc {args} { tailcall ::msgcat::mcn [namespace current] {*}$args } namespace import ::msgcat::mcpackagelocale } @@ -287,6 +278,13 @@ proc ::tcl::clock::Initialize {} { variable FEB_28 58 + # Default configuration + + ::tcl::unsupported::clock::configure -current-locale [mclocale] + #::tcl::unsupported::clock::configure -default-locale C + #::tcl::unsupported::clock::configure -year-century 2000 \ + # -century-switch 38 + # Translation table to map Windows TZI onto cities, so that the Olson # rules can apply. In some cases the mapping is ambiguous, so it's wise # to specify $::env(TCL_TZ) rather than simply depending on the system @@ -383,152 +381,6 @@ proc ::tcl::clock::Initialize {} { {46800 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Pacific/Tongatapu }] - # Groups of fields that specify the date, priorities, and code bursts that - # determine Julian Day Number given those groups. The code in [clock - # scan] will choose the highest priority (lowest numbered) set of fields - # that determines the date. - - variable DateParseActions { - - { seconds } 0 {} - - { julianDay } 1 {} - - { era century yearOfCentury month dayOfMonth } 2 { - dict set date year [expr { 100 * [dict get $date century] - + [dict get $date yearOfCentury] }] - set date [GetJulianDayFromEraYearMonthDay $date[set date {}] \ - $changeover] - } - { era century yearOfCentury dayOfYear } 2 { - dict set date year [expr { 100 * [dict get $date century] - + [dict get $date yearOfCentury] }] - set date [GetJulianDayFromEraYearDay $date[set date {}] \ - $changeover] - } - - { century yearOfCentury month dayOfMonth } 3 { - dict set date era CE - dict set date year [expr { 100 * [dict get $date century] - + [dict get $date yearOfCentury] }] - set date [GetJulianDayFromEraYearMonthDay $date[set date {}] \ - $changeover] - } - { century yearOfCentury dayOfYear } 3 { - dict set date era CE - dict set date year [expr { 100 * [dict get $date century] - + [dict get $date yearOfCentury] }] - set date [GetJulianDayFromEraYearDay $date[set date {}] \ - $changeover] - } - { iso8601Century iso8601YearOfCentury iso8601Week dayOfWeek } 3 { - dict set date era CE - dict set date iso8601Year \ - [expr { 100 * [dict get $date iso8601Century] - + [dict get $date iso8601YearOfCentury] }] - set date [GetJulianDayFromEraYearWeekDay $date[set date {}] \ - $changeover] - } - - { yearOfCentury month dayOfMonth } 4 { - set date [InterpretTwoDigitYear $date[set date {}] $baseTime] - dict set date era CE - set date [GetJulianDayFromEraYearMonthDay $date[set date {}] \ - $changeover] - } - { yearOfCentury dayOfYear } 4 { - set date [InterpretTwoDigitYear $date[set date {}] $baseTime] - dict set date era CE - set date [GetJulianDayFromEraYearDay $date[set date {}] \ - $changeover] - } - { iso8601YearOfCentury iso8601Week dayOfWeek } 4 { - set date [InterpretTwoDigitYear \ - $date[set date {}] $baseTime \ - iso8601YearOfCentury iso8601Year] - dict set date era CE - set date [GetJulianDayFromEraYearWeekDay $date[set date {}] \ - $changeover] - } - - { month dayOfMonth } 5 { - set date [AssignBaseYear $date[set date {}] \ - $baseTime $timeZone $changeover] - set date [GetJulianDayFromEraYearMonthDay $date[set date {}] \ - $changeover] - } - { dayOfYear } 5 { - set date [AssignBaseYear $date[set date {}] \ - $baseTime $timeZone $changeover] - set date [GetJulianDayFromEraYearDay $date[set date {}] \ - $changeover] - } - { iso8601Week dayOfWeek } 5 { - set date [AssignBaseIso8601Year $date[set date {}] \ - $baseTime $timeZone $changeover] - set date [GetJulianDayFromEraYearWeekDay $date[set date {}] \ - $changeover] - } - - { dayOfMonth } 6 { - set date [AssignBaseMonth $date[set date {}] \ - $baseTime $timeZone $changeover] - set date [GetJulianDayFromEraYearMonthDay $date[set date {}] \ - $changeover] - } - - { dayOfWeek } 7 { - set date [AssignBaseWeek $date[set date {}] \ - $baseTime $timeZone $changeover] - set date [GetJulianDayFromEraYearWeekDay $date[set date {}] \ - $changeover] - } - - {} 8 { - set date [AssignBaseJulianDay $date[set date {}] \ - $baseTime $timeZone $changeover] - } - } - - # Groups of fields that specify time of day, priorities, and code that - # processes them - - variable TimeParseActions { - - seconds 1 {} - - { hourAMPM minute second amPmIndicator } 2 { - dict set date secondOfDay [InterpretHMSP $date] - } - { hour minute second } 2 { - dict set date secondOfDay [InterpretHMS $date] - } - - { hourAMPM minute amPmIndicator } 3 { - dict set date second 0 - dict set date secondOfDay [InterpretHMSP $date] - } - { hour minute } 3 { - dict set date second 0 - dict set date secondOfDay [InterpretHMS $date] - } - - { hourAMPM amPmIndicator } 4 { - dict set date minute 0 - dict set date second 0 - dict set date secondOfDay [InterpretHMSP $date] - } - { hour } 4 { - dict set date minute 0 - dict set date second 0 - dict set date secondOfDay [InterpretHMS $date] - } - - { } 5 { - dict set date secondOfDay 0 - } - } - # Legacy time zones, used primarily for parsing RFC822 dates. variable LegacyTimeZone [dict create \ @@ -625,17 +477,9 @@ proc ::tcl::clock::Initialize {} { # Caches - variable LocaleNumeralCache {}; # Dictionary whose keys are locale - # names and whose values are pairs - # comprising regexes matching numerals - # in the given locales and dictionaries - # mapping the numerals to their numeric - # values. - # variable CachedSystemTimeZone; # If 'CachedSystemTimeZone' exists, - # it contains the value of the - # system time zone, as determined from - # the environment. - variable TimeZoneBad {}; # Dictionary whose keys are time zone + variable LocFmtMap [dict create]; # Dictionary with localized format maps + + variable TimeZoneBad [dict create]; # Dictionary whose keys are time zone # names and whose values are 1 if # the time zone is unknown and 0 # if it is known. @@ -644,1698 +488,204 @@ proc ::tcl::clock::Initialize {} { # comprising start time, UTC offset, # Daylight Saving Time indicator, and # time zone abbreviation. - variable FormatProc; # Array mapping format group - # and locale to the name of a procedure - # that renders the given format + + variable mcLocales [dict create]; # Dictionary with loaded locales + variable mcMergedCat [dict create]; # Dictionary with merged locale catalogs } ::tcl::clock::Initialize #---------------------------------------------------------------------- -# -# clock format -- -# -# 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. -# -#---------------------------------------------------------------------- - -proc ::tcl::clock::format { args } { - variable FormatProc - variable TZData - - lassign [ParseFormatArgs {*}$args] format locale timezone - set locale [string tolower $locale] - set clockval [lindex $args 0] - - # Get the data for time changes in the given zone - - if {$timezone eq ""} { - set timezone [GetSystemTimeZone] - } - if {![info exists TZData($timezone)]} { - if {[catch {SetupTimeZone $timezone} retval opts]} { - dict unset opts -errorinfo - return -options $opts $retval - } - } - - # Build a procedure to format the result. Cache the built procedure's name - # in the 'FormatProc' array to avoid losing its internal representation, - # which contains the name resolution. - - set procName formatproc'$format'$locale - set procName [namespace current]::[string map {: {\:} \\ {\\}} $procName] - if {[info exists FormatProc($procName)]} { - set procName $FormatProc($procName) - } else { - set FormatProc($procName) \ - [ParseClockFormatFormat $procName $format $locale] - } - - return [$procName $clockval $timezone] - -} - -#---------------------------------------------------------------------- +# mcget -- # -# ParseClockFormatFormat -- +# Return the merged translation catalog for the ::tcl::clock namespace +# Searching of catalog is similar to "msgcat::mc". # -# Builds and caches a procedure that formats a time value. +# Contrary to "msgcat::mc" may additionally load a package catalog +# on demand. # -# Parameters: -# format -- Format string to use -# locale -- Locale in which the format string is to be interpreted +# Arguments: +# loc The locale used for translation. # # Results: -# Returns the name of the newly-built procedure. -# -#---------------------------------------------------------------------- - -proc ::tcl::clock::ParseClockFormatFormat {procName format locale} { - - if {[namespace which $procName] ne {}} { - return $procName - } - - # Map away the locale-dependent composite format groups - - EnterLocale $locale - - # Change locale if a fresh locale has been given on the command line. - - try { - return [ParseClockFormatFormat2 $format $locale $procName] - } trap CLOCK {result opts} { - dict unset opts -errorinfo - return -options $opts $result - } -} - -proc ::tcl::clock::ParseClockFormatFormat2 {format locale procName} { - set didLocaleEra 0 - set didLocaleNumerals 0 - set preFormatCode \ - [string map [list @GREGORIAN_CHANGE_DATE@ \ - [mc GREGORIAN_CHANGE_DATE]] \ - { - variable TZData - set date [GetDateFields $clockval \ - $TZData($timezone) \ - @GREGORIAN_CHANGE_DATE@] - }] - set formatString {} - set substituents {} - set state {} - - set format [LocalizeFormat $locale $format] - - foreach char [split $format {}] { - switch -exact -- $state { - {} { - if { [string equal % $char] } { - set state percent - } else { - append formatString $char - } - } - percent { # Character following a '%' character - set state {} - switch -exact -- $char { - % { # A literal character, '%' - append formatString %% - } - a { # Day of week, abbreviated - append formatString %s - append substituents \ - [string map \ - [list @DAYS_OF_WEEK_ABBREV@ \ - [list [mc DAYS_OF_WEEK_ABBREV]]] \ - { [lindex @DAYS_OF_WEEK_ABBREV@ \ - [expr {[dict get $date dayOfWeek] \ - % 7}]]}] - } - A { # Day of week, spelt out. - append formatString %s - append substituents \ - [string map \ - [list @DAYS_OF_WEEK_FULL@ \ - [list [mc DAYS_OF_WEEK_FULL]]] \ - { [lindex @DAYS_OF_WEEK_FULL@ \ - [expr {[dict get $date dayOfWeek] \ - % 7}]]}] - } - b - h { # Name of month, abbreviated. - append formatString %s - append substituents \ - [string map \ - [list @MONTHS_ABBREV@ \ - [list [mc MONTHS_ABBREV]]] \ - { [lindex @MONTHS_ABBREV@ \ - [expr {[dict get $date month]-1}]]}] - } - B { # Name of month, spelt out - append formatString %s - append substituents \ - [string map \ - [list @MONTHS_FULL@ \ - [list [mc MONTHS_FULL]]] \ - { [lindex @MONTHS_FULL@ \ - [expr {[dict get $date month]-1}]]}] - } - C { # Century number - append formatString %02d - append substituents \ - { [expr {[dict get $date year] / 100}]} - } - d { # Day of month, with leading zero - append formatString %02d - append substituents { [dict get $date dayOfMonth]} - } - e { # Day of month, without leading zero - append formatString %2d - append substituents { [dict get $date dayOfMonth]} - } - E { # Format group in a locale-dependent - # alternative era - set state percentE - if {!$didLocaleEra} { - append preFormatCode \ - [string map \ - [list @LOCALE_ERAS@ \ - [list [mc LOCALE_ERAS]]] \ - { - set date [GetLocaleEra \ - $date[set date {}] \ - @LOCALE_ERAS@]}] \n - set didLocaleEra 1 - } - if {!$didLocaleNumerals} { - append preFormatCode \ - [list set localeNumerals \ - [mc LOCALE_NUMERALS]] \n - set didLocaleNumerals 1 - } - } - g { # Two-digit year relative to ISO8601 - # week number - append formatString %02d - append substituents \ - { [expr { [dict get $date iso8601Year] % 100 }]} - } - G { # Four-digit year relative to ISO8601 - # week number - append formatString %02d - append substituents { [dict get $date iso8601Year]} - } - H { # Hour in the 24-hour day, leading zero - append formatString %02d - append substituents \ - { [expr { [dict get $date localSeconds] \ - / 3600 % 24}]} - } - I { # Hour AM/PM, with leading zero - append formatString %02d - append substituents \ - { [expr { ( ( ( [dict get $date localSeconds] \ - % 86400 ) \ - + 86400 \ - - 3600 ) \ - / 3600 ) \ - % 12 + 1 }] } - } - j { # Day of year (001-366) - append formatString %03d - append substituents { [dict get $date dayOfYear]} - } - J { # Julian Day Number - append formatString %07ld - append substituents { [dict get $date julianDay]} - } - k { # Hour (0-23), no leading zero - append formatString %2d - append substituents \ - { [expr { [dict get $date localSeconds] - / 3600 - % 24 }]} - } - l { # Hour (12-11), no leading zero - append formatString %2d - append substituents \ - { [expr { ( ( ( [dict get $date localSeconds] - % 86400 ) - + 86400 - - 3600 ) - / 3600 ) - % 12 + 1 }]} - } - m { # Month number, leading zero - append formatString %02d - append substituents { [dict get $date month]} - } - M { # Minute of the hour, leading zero - append formatString %02d - append substituents \ - { [expr { [dict get $date localSeconds] - / 60 - % 60 }]} - } - n { # A literal newline - append formatString \n - } - N { # Month number, no leading zero - append formatString %2d - append substituents { [dict get $date month]} - } - O { # A format group in the locale's - # alternative numerals - set state percentO - if {!$didLocaleNumerals} { - append preFormatCode \ - [list set localeNumerals \ - [mc LOCALE_NUMERALS]] \n - set didLocaleNumerals 1 - } - } - p { # Localized 'AM' or 'PM' indicator - # converted to uppercase - append formatString %s - append preFormatCode \ - [list set AM [string toupper [mc AM]]] \n \ - [list set PM [string toupper [mc PM]]] \n - append substituents \ - { [expr {(([dict get $date localSeconds] - % 86400) < 43200) ? - $AM : $PM}]} - } - P { # Localized 'AM' or 'PM' indicator - append formatString %s - append preFormatCode \ - [list set am [mc AM]] \n \ - [list set pm [mc PM]] \n - append substituents \ - { [expr {(([dict get $date localSeconds] - % 86400) < 43200) ? - $am : $pm}]} - - } - Q { # Hi, Jeff! - append formatString %s - append substituents { [FormatStarDate $date]} - } - s { # Seconds from the Posix Epoch - append formatString %s - append substituents { [dict get $date seconds]} - } - S { # Second of the minute, with - # leading zero - append formatString %02d - append substituents \ - { [expr { [dict get $date localSeconds] - % 60 }]} - } - t { # A literal tab character - append formatString \t - } - u { # Day of the week (1-Monday, 7-Sunday) - append formatString %1d - append substituents { [dict get $date dayOfWeek]} - } - U { # Week of the year (00-53). The - # first Sunday of the year is the - # first day of week 01 - append formatString %02d - append preFormatCode { - set dow [dict get $date dayOfWeek] - if { $dow == 7 } { - set dow 0 - } - incr dow - set UweekNumber \ - [expr { ( [dict get $date dayOfYear] - - $dow + 7 ) - / 7 }] - } - append substituents { $UweekNumber} - } - V { # The ISO8601 week number - append formatString %02d - append substituents { [dict get $date iso8601Week]} - } - w { # Day of the week (0-Sunday, - # 6-Saturday) - append formatString %1d - append substituents \ - { [expr { [dict get $date dayOfWeek] % 7 }]} - } - W { # Week of the year (00-53). The first - # Monday of the year is the first day - # of week 01. - append preFormatCode { - set WweekNumber \ - [expr { ( [dict get $date dayOfYear] - - [dict get $date dayOfWeek] - + 7 ) - / 7 }] - } - append formatString %02d - append substituents { $WweekNumber} - } - y { # The two-digit year of the century - append formatString %02d - append substituents \ - { [expr { [dict get $date year] % 100 }]} - } - Y { # The four-digit year - append formatString %04d - append substituents { [dict get $date year]} - } - z { # The time zone as hours and minutes - # east (+) or west (-) of Greenwich - append formatString %s - append substituents { [FormatNumericTimeZone \ - [dict get $date tzOffset]]} - } - Z { # The name of the time zone - append formatString %s - append substituents { [dict get $date tzName]} - } - % { # A literal percent character - append formatString %% - } - default { # An unknown escape sequence - append formatString %% $char - } - } - } - percentE { # Character following %E - set state {} - switch -exact -- $char { - E { - append formatString %s - append substituents { } \ - [string map \ - [list @BCE@ [list [mc BCE]] \ - @CE@ [list [mc CE]]] \ - {[dict get {BCE @BCE@ CE @CE@} \ - [dict get $date era]]}] - } - C { # Locale-dependent era - append formatString %s - append substituents { [dict get $date localeEra]} - } - y { # Locale-dependent year of the era - append preFormatCode { - set y [dict get $date localeYear] - if { $y >= 0 && $y < 100 } { - set Eyear [lindex $localeNumerals $y] - } else { - set Eyear $y - } - } - append formatString %s - append substituents { $Eyear} - } - default { # Unknown %E format group - append formatString %%E $char - } - } - } - percentO { # Character following %O - set state {} - switch -exact -- $char { - d - e { # Day of the month in alternative - # numerals - append formatString %s - append substituents \ - { [lindex $localeNumerals \ - [dict get $date dayOfMonth]]} - } - H - k { # Hour of the day in alternative - # numerals - append formatString %s - append substituents \ - { [lindex $localeNumerals \ - [expr { [dict get $date localSeconds] - / 3600 - % 24 }]]} - } - I - l { # Hour (12-11) AM/PM in alternative - # numerals - append formatString %s - append substituents \ - { [lindex $localeNumerals \ - [expr { ( ( ( [dict get $date localSeconds] - % 86400 ) - + 86400 - - 3600 ) - / 3600 ) - % 12 + 1 }]]} - } - m { # Month number in alternative numerals - append formatString %s - append substituents \ - { [lindex $localeNumerals [dict get $date month]]} - } - M { # Minute of the hour in alternative - # numerals - append formatString %s - append substituents \ - { [lindex $localeNumerals \ - [expr { [dict get $date localSeconds] - / 60 - % 60 }]]} - } - S { # Second of the minute in alternative - # numerals - append formatString %s - append substituents \ - { [lindex $localeNumerals \ - [expr { [dict get $date localSeconds] - % 60 }]]} - } - u { # Day of the week (Monday=1,Sunday=7) - # in alternative numerals - append formatString %s - append substituents \ - { [lindex $localeNumerals \ - [dict get $date dayOfWeek]]} - } - w { # Day of the week (Sunday=0,Saturday=6) - # in alternative numerals - append formatString %s - append substituents \ - { [lindex $localeNumerals \ - [expr { [dict get $date dayOfWeek] % 7 }]]} - } - y { # Year of the century in alternative - # numerals - append formatString %s - append substituents \ - { [lindex $localeNumerals \ - [expr { [dict get $date year] % 100 }]]} - } - default { # Unknown format group - append formatString %%O $char - } - } - } - } - } - - # Clean up any improperly terminated groups - - switch -exact -- $state { - percent { - append formatString %% - } - percentE { - append retval %%E - } - percentO { - append retval %%O - } - } - - proc $procName {clockval timezone} " - $preFormatCode - return \[::format [list $formatString] $substituents\] - " - - # puts [list $procName [info args $procName] [info body $procName]] - - return $procName -} - -#---------------------------------------------------------------------- -# -# clock scan -- -# -# 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. +# Returns the dictionary object as whole catalog of the package/locale. # -#---------------------------------------------------------------------- - -proc ::tcl::clock::scan { args } { - - set format {} - - # Check the count of args - - if { [llength $args] < 1 || [llength $args] % 2 != 1 } { - set cmdName "clock scan" - return -code error \ - -errorcode [list CLOCK wrongNumArgs] \ - "wrong \# args: should be\ - \"$cmdName string\ - ?-base seconds?\ - ?-format string? ?-gmt boolean?\ - ?-locale LOCALE? ?-timezone ZONE?\"" +proc ::tcl::clock::mcget {loc} { + variable mcMergedCat + switch -- $loc system { + set loc [GetSystemLocale] + } current { + set loc [mclocale] } - - # Set defaults - - set base [clock seconds] - set string [lindex $args 0] - set format {} - set gmt 0 - set locale c - set timezone [GetSystemTimeZone] - - # Pick up command line options. - - foreach { flag value } [lreplace $args 0 0] { - switch -exact -- $flag { - -b - -ba - -bas - -base { - set base $value - } - -f - -fo - -for - -form - -forma - -format { - set saw(-format) {} - set format $value - } - -g - -gm - -gmt { - set saw(-gmt) {} - set gmt $value - } - -l - -lo - -loc - -loca - -local - -locale { - set saw(-locale) {} - set locale [string tolower $value] - } - -t - -ti - -tim - -time - -timez - -timezo - -timezon - -timezone { - set saw(-timezone) {} - set timezone $value - } - default { - return -code error \ - -errorcode [list CLOCK badOption $flag] \ - "bad option \"$flag\":\ - must be -base, -format, -gmt, -locale, or -timezone" - } - } - } - - # Check options for validity - - if { [info exists saw(-gmt)] && [info exists saw(-timezone)] } { - return -code error \ - -errorcode [list CLOCK gmtWithTimezone] \ - "cannot use -gmt and -timezone in same call" - } - if { [catch { expr { wide($base) } } result] } { - return -code error "expected integer but got \"$base\"" - } - if { ![string is boolean -strict $gmt] } { - return -code error "expected boolean value but got \"$gmt\"" - } elseif { $gmt } { - set timezone :GMT + if {$loc ne {}} { + set loc [string tolower $loc] } - if { ![info exists saw(-format)] } { - # Perhaps someday we'll localize the legacy code. Right now, it's not - # localized. - if { [info exists saw(-locale)] } { - return -code error \ - -errorcode [list CLOCK flagWithLegacyFormat] \ - "legacy \[clock scan\] does not support -locale" - - } - return [FreeScan $string $base $timezone $locale] + # try to retrieve now if already available: + if {[dict exists $mcMergedCat $loc]} { + return [dict get $mcMergedCat $loc] } - # Change locale if a fresh locale has been given on the command line. - - EnterLocale $locale - - try { - # Map away the locale-dependent composite format groups - - set scanner [ParseClockScanFormat $format $locale] - return [$scanner $string $base $timezone] - } trap CLOCK {result opts} { - # Conceal location of generation of expected errors - dict unset opts -errorinfo - return -options $opts $result - } + # get locales list for given locale (de_de -> {de_de de {}}) + variable mcLocales + if {[dict exists $mcLocales $loc]} { + set loclist [dict get $mcLocales $loc] + } else { + # save current locale: + set prevloc [mclocale] + # lazy load catalog on demand (set it will load the catalog) + mcpackagelocale set $loc + set loclist [msgcat::mcutil::getpreferences $loc] + dict set $mcLocales $loc $loclist + # restore: + if {$prevloc ne $loc} { + mcpackagelocale set $prevloc + } + } + # get whole catalog: + mcMerge $loclist } -#---------------------------------------------------------------------- +# mcMerge -- # -# FreeScan -- +# Merge message catalog dictionaries to one dictionary. # -# Scans a time in free format -# -# Parameters: -# string - String containing the time to scan -# base - Base time, expressed in seconds from the Epoch -# timezone - Default time zone in which the time will be expressed -# locale - (Unused) Name of the locale where the time will be scanned. +# Arguments: +# locales List of locales to merge. # # Results: -# Returns the date and time extracted from the string in seconds from -# the epoch -# -#---------------------------------------------------------------------- - -proc ::tcl::clock::FreeScan { string base timezone locale } { - - variable TZData - - # Get the data for time changes in the given zone - - try { - SetupTimeZone $timezone - } on error {retval opts} { - dict unset opts -errorinfo - return -options $opts $retval - } - - # Extract year, month and day from the base time for the parser to use as - # defaults - - set date [GetDateFields $base $TZData($timezone) 2361222] - dict set date secondOfDay [expr { - [dict get $date localSeconds] % 86400 - }] - - # Parse the date. The parser will return a list comprising date, time, - # time zone, relative month/day/seconds, relative weekday, ordinal month. - - try { - set scanned [Oldscan $string \ - [dict get $date year] \ - [dict get $date month] \ - [dict get $date dayOfMonth]] - lassign $scanned \ - parseDate parseTime parseZone parseRel \ - parseWeekday parseOrdinalMonth - } on error message { - return -code error \ - "unable to convert date-time string \"$string\": $message" - } - - # If the caller supplied a date in the string, update the 'date' dict with - # the value. If the caller didn't specify a time with the date, default to - # midnight. - - if { [llength $parseDate] > 0 } { - lassign $parseDate y m d - if { $y < 100 } { - if { $y >= 39 } { - incr y 1900 - } else { - incr y 2000 - } - } - dict set date era CE - dict set date year $y - dict set date month $m - dict set date dayOfMonth $d - if { $parseTime eq {} } { - set parseTime 0 - } - } - - # If the caller supplied a time zone in the string, it comes back as a - # two-element list; the first element is the number of minutes east of - # Greenwich, and the second is a Daylight Saving Time indicator (1 == yes, - # 0 == no, -1 == unknown). We make it into a time zone indicator of - # +-hhmm. - - if { [llength $parseZone] > 0 } { - lassign $parseZone minEast dstFlag - set timezone [FormatNumericTimeZone \ - [expr { 60 * $minEast + 3600 * $dstFlag }]] - SetupTimeZone $timezone - } - dict set date tzName $timezone - - # Assemble date, time, zone into seconds-from-epoch - - set date [GetJulianDayFromEraYearMonthDay $date[set date {}] 2361222] - if { $parseTime ne {} } { - dict set date secondOfDay $parseTime - } elseif { [llength $parseWeekday] != 0 - || [llength $parseOrdinalMonth] != 0 - || ( [llength $parseRel] != 0 - && ( [lindex $parseRel 0] != 0 - || [lindex $parseRel 1] != 0 ) ) } { - dict set date secondOfDay 0 - } - - dict set date localSeconds [expr { - -210866803200 - + ( 86400 * wide([dict get $date julianDay]) ) - + [dict get $date secondOfDay] - }] - dict set date tzName $timezone - set date [ConvertLocalToUTC $date[set date {}] $TZData($timezone) 2361222] - set seconds [dict get $date seconds] - - # Do relative times - - if { [llength $parseRel] > 0 } { - lassign $parseRel relMonth relDay relSecond - set seconds [add $seconds \ - $relMonth months $relDay days $relSecond seconds \ - -timezone $timezone -locale $locale] - } - - # Do relative weekday - - if { [llength $parseWeekday] > 0 } { - lassign $parseWeekday dayOrdinal dayOfWeek - set date2 [GetDateFields $seconds $TZData($timezone) 2361222] - dict set date2 era CE - set jdwkday [WeekdayOnOrBefore $dayOfWeek [expr { - [dict get $date2 julianDay] + 6 - }]] - incr jdwkday [expr { 7 * $dayOrdinal }] - if { $dayOrdinal > 0 } { - incr jdwkday -7 - } - dict set date2 secondOfDay \ - [expr { [dict get $date2 localSeconds] % 86400 }] - dict set date2 julianDay $jdwkday - dict set date2 localSeconds [expr { - -210866803200 - + ( 86400 * wide([dict get $date2 julianDay]) ) - + [dict get $date secondOfDay] - }] - dict set date2 tzName $timezone - set date2 [ConvertLocalToUTC $date2[set date2 {}] $TZData($timezone) \ - 2361222] - set seconds [dict get $date2 seconds] - - } - - # Do relative month - - if { [llength $parseOrdinalMonth] > 0 } { - lassign $parseOrdinalMonth monthOrdinal monthNumber - if { $monthOrdinal > 0 } { - set monthDiff [expr { $monthNumber - [dict get $date month] }] - if { $monthDiff <= 0 } { - incr monthDiff 12 - } - incr monthOrdinal -1 +# Returns the (weak pointer) to merged dictionary of message catalog. +# +proc ::tcl::clock::mcMerge {locales} { + variable mcMergedCat + if {[dict exists $mcMergedCat [set loc [lindex $locales 0]]]} { + return [dict get $mcMergedCat $loc] + } + # package msgcat currently does not provide possibility to get whole catalog: + upvar ::msgcat::Msgs Msgs + set ns ::tcl::clock + # Merge sequential locales (in reverse order, e. g. {} -> en -> en_en): + if {[llength $locales] > 1} { + set mrgcat [mcMerge [lrange $locales 1 end]] + if {[dict exists $Msgs $ns $loc]} { + set mrgcat [dict merge $mrgcat [dict get $Msgs $ns $loc]] + dict set mrgcat L $loc } else { - set monthDiff [expr { [dict get $date month] - $monthNumber }] - if { $monthDiff >= 0 } { - incr monthDiff -12 - } - incr monthOrdinal - } - set seconds [add $seconds $monthOrdinal years $monthDiff months \ - -timezone $timezone -locale $locale] - } - - return $seconds -} - - -#---------------------------------------------------------------------- -# -# ParseClockScanFormat -- -# -# Parses a format string given to [clock scan -format] -# -# Parameters: -# formatString - The format being parsed -# locale - The current locale -# -# Results: -# Constructs and returns a procedure that accepts the string being -# scanned, the base time, and the time zone. The procedure will either -# return the scanned time or else throw an error that should be rethrown -# to the caller of [clock scan] -# -# Side effects: -# The given procedure is defined in the ::tcl::clock namespace. Scan -# procedures are not deleted once installed. -# -# Why do we parse dates by defining a procedure to parse them? The reason is -# that by doing so, we have one convenient place to cache all the information: -# the regular expressions that match the patterns (which will be compiled), -# the code that assembles the date information, everything lands in one place. -# In this way, when a given format is reused at run time, all the information -# of how to apply it is available in a single place. -# -#---------------------------------------------------------------------- - -proc ::tcl::clock::ParseClockScanFormat {formatString locale} { - # Check whether the format has been parsed previously, and return the - # existing recognizer if it has. - - set procName scanproc'$formatString'$locale - set procName [namespace current]::[string map {: {\:} \\ {\\}} $procName] - if { [namespace which $procName] != {} } { - return $procName - } - - variable DateParseActions - variable TimeParseActions - - # Localize the %x, %X, etc. groups - - set formatString [LocalizeFormat $locale $formatString] - - # Condense whitespace - - regsub -all {[[:space:]]+} $formatString { } formatString - - # Walk through the groups of the format string. In this loop, we - # accumulate: - # - a regular expression that matches the string, - # - the count of capturing brackets in the regexp - # - a set of code that post-processes the fields captured by the regexp, - # - a dictionary whose keys are the names of fields that are present - # in the format string. - - set re {^[[:space:]]*} - set captureCount 0 - set postcode {} - set fieldSet [dict create] - set fieldCount 0 - set postSep {} - set state {} - - foreach c [split $formatString {}] { - switch -exact -- $state { - {} { - if { $c eq "%" } { - set state % - } elseif { $c eq " " } { - append re {[[:space:]]+} - } else { - if { ! [string is alnum $c] } { - append re "\\" - } - append re $c - } - } - % { - set state {} - switch -exact -- $c { - % { - append re % - } - { } { - append re "\[\[:space:\]\]*" - } - a - A { # Day of week, in words - set l {} - foreach \ - i {7 1 2 3 4 5 6} \ - abr [mc DAYS_OF_WEEK_ABBREV] \ - full [mc DAYS_OF_WEEK_FULL] { - dict set l [string tolower $abr] $i - dict set l [string tolower $full] $i - incr i - } - lassign [UniquePrefixRegexp $l] regex lookup - append re ( $regex ) - dict set fieldSet dayOfWeek [incr fieldCount] - append postcode "dict set date dayOfWeek \[" \ - "dict get " [list $lookup] " " \ - \[ {string tolower $field} [incr captureCount] \] \ - "\]\n" - } - b - B - h { # Name of month - set i 0 - set l {} - foreach \ - abr [mc MONTHS_ABBREV] \ - full [mc MONTHS_FULL] { - incr i - dict set l [string tolower $abr] $i - dict set l [string tolower $full] $i - } - lassign [UniquePrefixRegexp $l] regex lookup - append re ( $regex ) - dict set fieldSet month [incr fieldCount] - append postcode "dict set date month \[" \ - "dict get " [list $lookup] \ - " " \[ {string tolower $field} \ - [incr captureCount] \] \ - "\]\n" - } - C { # Gregorian century - append re \\s*(\\d\\d?) - dict set fieldSet century [incr fieldCount] - append postcode "dict set date century \[" \ - "::scan \$field" [incr captureCount] " %d" \ - "\]\n" - } - d - e { # Day of month - append re \\s*(\\d\\d?) - dict set fieldSet dayOfMonth [incr fieldCount] - append postcode "dict set date dayOfMonth \[" \ - "::scan \$field" [incr captureCount] " %d" \ - "\]\n" - } - E { # Prefix for locale-specific codes - set state %E - } - g { # ISO8601 2-digit year - append re \\s*(\\d\\d) - dict set fieldSet iso8601YearOfCentury \ - [incr fieldCount] - append postcode \ - "dict set date iso8601YearOfCentury \[" \ - "::scan \$field" [incr captureCount] " %d" \ - "\]\n" - } - G { # ISO8601 4-digit year - append re \\s*(\\d\\d)(\\d\\d) - dict set fieldSet iso8601Century [incr fieldCount] - dict set fieldSet iso8601YearOfCentury \ - [incr fieldCount] - append postcode \ - "dict set date iso8601Century \[" \ - "::scan \$field" [incr captureCount] " %d" \ - "\]\n" \ - "dict set date iso8601YearOfCentury \[" \ - "::scan \$field" [incr captureCount] " %d" \ - "\]\n" - } - H - k { # Hour of day - append re \\s*(\\d\\d?) - dict set fieldSet hour [incr fieldCount] - append postcode "dict set date hour \[" \ - "::scan \$field" [incr captureCount] " %d" \ - "\]\n" - } - I - l { # Hour, AM/PM - append re \\s*(\\d\\d?) - dict set fieldSet hourAMPM [incr fieldCount] - append postcode "dict set date hourAMPM \[" \ - "::scan \$field" [incr captureCount] " %d" \ - "\]\n" - } - j { # Day of year - append re \\s*(\\d\\d?\\d?) - dict set fieldSet dayOfYear [incr fieldCount] - append postcode "dict set date dayOfYear \[" \ - "::scan \$field" [incr captureCount] " %d" \ - "\]\n" - } - J { # Julian Day Number - append re \\s*(\\d+) - dict set fieldSet julianDay [incr fieldCount] - append postcode "dict set date julianDay \[" \ - "::scan \$field" [incr captureCount] " %ld" \ - "\]\n" - } - m - N { # Month number - append re \\s*(\\d\\d?) - dict set fieldSet month [incr fieldCount] - append postcode "dict set date month \[" \ - "::scan \$field" [incr captureCount] " %d" \ - "\]\n" - } - M { # Minute - append re \\s*(\\d\\d?) - dict set fieldSet minute [incr fieldCount] - append postcode "dict set date minute \[" \ - "::scan \$field" [incr captureCount] " %d" \ - "\]\n" - } - n { # Literal newline - append re \\n - } - O { # Prefix for locale numerics - set state %O - } - p - P { # AM/PM indicator - set l [list [string tolower [mc AM]] 0 \ - [string tolower [mc PM]] 1] - lassign [UniquePrefixRegexp $l] regex lookup - append re ( $regex ) - dict set fieldSet amPmIndicator [incr fieldCount] - append postcode "dict set date amPmIndicator \[" \ - "dict get " [list $lookup] " \[string tolower " \ - "\$field" \ - [incr captureCount] \ - "\]\]\n" - } - Q { # Hi, Jeff! - append re {Stardate\s+([-+]?\d+)(\d\d\d)[.](\d)} - incr captureCount - dict set fieldSet seconds [incr fieldCount] - append postcode {dict set date seconds } \[ \ - {ParseStarDate $field} [incr captureCount] \ - { $field} [incr captureCount] \ - { $field} [incr captureCount] \ - \] \n - } - s { # Seconds from Posix Epoch - # This next case is insanely difficult, because it's - # problematic to determine whether the field is - # actually within the range of a wide integer. - append re {\s*([-+]?\d+)} - dict set fieldSet seconds [incr fieldCount] - append postcode {dict set date seconds } \[ \ - {ScanWide $field} [incr captureCount] \] \n - } - S { # Second - append re \\s*(\\d\\d?) - dict set fieldSet second [incr fieldCount] - append postcode "dict set date second \[" \ - "::scan \$field" [incr captureCount] " %d" \ - "\]\n" - } - t { # Literal tab character - append re \\t - } - u - w { # Day number within week, 0 or 7 == Sun - # 1=Mon, 6=Sat - append re \\s*(\\d) - dict set fieldSet dayOfWeek [incr fieldCount] - append postcode {::scan $field} [incr captureCount] \ - { %d dow} \n \ - { - if { $dow == 0 } { - set dow 7 - } elseif { $dow > 7 } { - return -code error \ - -errorcode [list CLOCK badDayOfWeek] \ - "day of week is greater than 7" - } - dict set date dayOfWeek $dow - } - } - U { # Week of year. The first Sunday of - # the year is the first day of week - # 01. No scan rule uses this group. - append re \\s*\\d\\d? - } - V { # Week of ISO8601 year - - append re \\s*(\\d\\d?) - dict set fieldSet iso8601Week [incr fieldCount] - append postcode "dict set date iso8601Week \[" \ - "::scan \$field" [incr captureCount] " %d" \ - "\]\n" - } - W { # Week of the year (00-53). The first - # Monday of the year is the first day - # of week 01. No scan rule uses this - # group. - append re \\s*\\d\\d? - } - y { # Two-digit Gregorian year - append re \\s*(\\d\\d?) - dict set fieldSet yearOfCentury [incr fieldCount] - append postcode "dict set date yearOfCentury \[" \ - "::scan \$field" [incr captureCount] " %d" \ - "\]\n" - } - Y { # 4-digit Gregorian year - append re \\s*(\\d\\d)(\\d\\d) - dict set fieldSet century [incr fieldCount] - dict set fieldSet yearOfCentury [incr fieldCount] - append postcode \ - "dict set date century \[" \ - "::scan \$field" [incr captureCount] " %d" \ - "\]\n" \ - "dict set date yearOfCentury \[" \ - "::scan \$field" [incr captureCount] " %d" \ - "\]\n" - } - z - Z { # Time zone name - append re {(?:([-+]\d\d(?::?\d\d(?::?\d\d)?)?)|([[:alnum:]]{1,4}))} - dict set fieldSet tzName [incr fieldCount] - append postcode \ - {if } \{ { $field} [incr captureCount] \ - { ne "" } \} { } \{ \n \ - {dict set date tzName $field} \ - $captureCount \n \ - \} { else } \{ \n \ - {dict set date tzName } \[ \ - {ConvertLegacyTimeZone $field} \ - [incr captureCount] \] \n \ - \} \n \ - } - % { # Literal percent character - append re % - } - default { - append re % - if { ! [string is alnum $c] } { - append re \\ - } - append re $c - } - } - } - %E { - switch -exact -- $c { - C { # Locale-dependent era - set d {} - foreach triple [mc LOCALE_ERAS] { - lassign $triple t symbol year - dict set d [string tolower $symbol] $year - } - lassign [UniquePrefixRegexp $d] regex lookup - append re (?: $regex ) - } - E { - set l {} - dict set l [string tolower [mc BCE]] BCE - dict set l [string tolower [mc CE]] CE - dict set l b.c.e. BCE - dict set l c.e. CE - dict set l b.c. BCE - dict set l a.d. CE - lassign [UniquePrefixRegexp $l] regex lookup - append re ( $regex ) - dict set fieldSet era [incr fieldCount] - append postcode "dict set date era \["\ - "dict get " [list $lookup] \ - { } \[ {string tolower $field} \ - [incr captureCount] \] \ - "\]\n" - } - y { # Locale-dependent year of the era - lassign [LocaleNumeralMatcher $locale] regex lookup - append re $regex - incr captureCount - } - default { - append re %E - if { ! [string is alnum $c] } { - append re \\ - } - append re $c - } - } - set state {} - } - %O { - switch -exact -- $c { - d - e { - lassign [LocaleNumeralMatcher $locale] regex lookup - append re $regex - dict set fieldSet dayOfMonth [incr fieldCount] - append postcode "dict set date dayOfMonth \[" \ - "dict get " [list $lookup] " \$field" \ - [incr captureCount] \ - "\]\n" - } - H - k { - lassign [LocaleNumeralMatcher $locale] regex lookup - append re $regex - dict set fieldSet hour [incr fieldCount] - append postcode "dict set date hour \[" \ - "dict get " [list $lookup] " \$field" \ - [incr captureCount] \ - "\]\n" - } - I - l { - lassign [LocaleNumeralMatcher $locale] regex lookup - append re $regex - dict set fieldSet hourAMPM [incr fieldCount] - append postcode "dict set date hourAMPM \[" \ - "dict get " [list $lookup] " \$field" \ - [incr captureCount] \ - "\]\n" - } - m { - lassign [LocaleNumeralMatcher $locale] regex lookup - append re $regex - dict set fieldSet month [incr fieldCount] - append postcode "dict set date month \[" \ - "dict get " [list $lookup] " \$field" \ - [incr captureCount] \ - "\]\n" - } - M { - lassign [LocaleNumeralMatcher $locale] regex lookup - append re $regex - dict set fieldSet minute [incr fieldCount] - append postcode "dict set date minute \[" \ - "dict get " [list $lookup] " \$field" \ - [incr captureCount] \ - "\]\n" - } - S { - lassign [LocaleNumeralMatcher $locale] regex lookup - append re $regex - dict set fieldSet second [incr fieldCount] - append postcode "dict set date second \[" \ - "dict get " [list $lookup] " \$field" \ - [incr captureCount] \ - "\]\n" - } - u - w { - lassign [LocaleNumeralMatcher $locale] regex lookup - append re $regex - dict set fieldSet dayOfWeek [incr fieldCount] - append postcode "set dow \[dict get " [list $lookup] \ - { $field} [incr captureCount] \] \n \ - { - if { $dow == 0 } { - set dow 7 - } elseif { $dow > 7 } { - return -code error \ - -errorcode [list CLOCK badDayOfWeek] \ - "day of week is greater than 7" - } - dict set date dayOfWeek $dow - } - } - y { - lassign [LocaleNumeralMatcher $locale] regex lookup - append re $regex - dict set fieldSet yearOfCentury [incr fieldCount] - append postcode {dict set date yearOfCentury } \[ \ - {dict get } [list $lookup] { $field} \ - [incr captureCount] \] \n - } - default { - append re %O - if { ! [string is alnum $c] } { - append re \\ - } - append re $c - } - } - set state {} - } + # be sure a duplicate is created, don't overwrite {} (common) locale: + set mrgcat [dict merge $mrgcat [dict create L $loc]] } - } - - # Clean up any unfinished format groups - - append re $state \\s*\$ - - # Build the procedure - - set procBody {} - append procBody "variable ::tcl::clock::TZData" \n - append procBody "if \{ !\[ regexp -nocase [list $re] \$string ->" - for { set i 1 } { $i <= $captureCount } { incr i } { - append procBody " " field $i - } - append procBody "\] \} \{" \n - append procBody { - return -code error -errorcode [list CLOCK badInputString] \ - {input string does not match supplied format} - } - append procBody \}\n - append procBody "set date \[dict create\]" \n - append procBody {dict set date tzName $timeZone} \n - append procBody $postcode - append procBody [list set changeover [mc GREGORIAN_CHANGE_DATE]] \n - - # Set up the time zone before doing anything with a default base date - # that might need a timezone to interpret it. - - if { ![dict exists $fieldSet seconds] - && ![dict exists $fieldSet starDate] } { - if { [dict exists $fieldSet tzName] } { - append procBody { - set timeZone [dict get $date tzName] - } - } - append procBody { - ::tcl::clock::SetupTimeZone $timeZone - } - } - - # Add code that gets Julian Day Number from the fields. - - append procBody [MakeParseCodeFromFields $fieldSet $DateParseActions] - - # Get time of day - - append procBody [MakeParseCodeFromFields $fieldSet $TimeParseActions] - - # Assemble seconds from the Julian day and second of the day. - # Convert to local time unless epoch seconds or stardate are - # being processed - they're always absolute - - if { ![dict exists $fieldSet seconds] - && ![dict exists $fieldSet starDate] } { - append procBody { - if { [dict get $date julianDay] > 5373484 } { - return -code error -errorcode [list CLOCK dateTooLarge] \ - "requested date too large to represent" - } - dict set date localSeconds [expr { - -210866803200 - + ( 86400 * wide([dict get $date julianDay]) ) - + [dict get $date secondOfDay] - }] - } - - # Finally, convert the date to local time - - append procBody { - set date [::tcl::clock::ConvertLocalToUTC $date[set date {}] \ - $TZData($timeZone) $changeover] - } - } - - # Return result - - append procBody {return [dict get $date seconds]} \n - - proc $procName { string baseTime timeZone } $procBody - - # puts [list proc $procName [list string baseTime timeZone] $procBody] - - return $procName -} - -#---------------------------------------------------------------------- -# -# LocaleNumeralMatcher -- -# -# Composes a regexp that captures the numerals in the given locale, and -# a dictionary to map them to conventional numerals. -# -# Parameters: -# locale - Name of the current locale -# -# Results: -# Returns a two-element list comprising the regexp and the dictionary. -# -# Side effects: -# Caches the result. -# -#---------------------------------------------------------------------- - -proc ::tcl::clock::LocaleNumeralMatcher {l} { - variable LocaleNumeralCache - - if { ![dict exists $LocaleNumeralCache $l] } { - set d {} - set i 0 - set sep \( - foreach n [mc LOCALE_NUMERALS] { - dict set d $n $i - regsub -all {[^[:alnum:]]} $n \\\\& subex - append re $sep $subex - set sep | - incr i + } else { + if {[dict exists $Msgs $ns $loc]} { + set mrgcat [dict get $Msgs $ns $loc] + dict set mrgcat L $loc + } else { + # be sure a duplicate is created, don't overwrite {} (common) locale: + set mrgcat [dict create L $loc] } - append re \) - dict set LocaleNumeralCache $l [list $re $d] } - return [dict get $LocaleNumeralCache $l] + dict set mcMergedCat $loc $mrgcat + # return smart reference (shared dict as object with exact one ref-counter) + return $mrgcat } - - #---------------------------------------------------------------------- # -# UniquePrefixRegexp -- +# GetSystemLocale -- # -# Composes a regexp that performs unique-prefix matching. The RE -# matches one of a supplied set of strings, or any unique prefix -# thereof. +# Determines the system locale, which corresponds to "system" +# keyword for locale parameter of 'clock' command. # # Parameters: -# data - List of alternating match-strings and values. -# Match-strings with distinct values are considered -# distinct. -# -# Results: -# Returns a two-element list. The first is a regexp that matches any -# unique prefix of any of the strings. The second is a dictionary whose -# keys are match values from the regexp and whose values are the -# corresponding values from 'data'. -# -# Side effects: # None. # -#---------------------------------------------------------------------- - -proc ::tcl::clock::UniquePrefixRegexp { data } { - # The 'successors' dictionary will contain, for each string that is a - # prefix of any key, all characters that may follow that prefix. The - # 'prefixMapping' dictionary will have keys that are prefixes of keys and - # values that correspond to the keys. - - set prefixMapping [dict create] - set successors [dict create {} {}] - - # Walk the key-value pairs - - foreach { key value } $data { - # Construct all prefixes of the key; - - set prefix {} - foreach char [split $key {}] { - set oldPrefix $prefix - dict set successors $oldPrefix $char {} - append prefix $char - - # Put the prefixes in the 'prefixMapping' and 'successors' - # dictionaries - - dict lappend prefixMapping $prefix $value - if { ![dict exists $successors $prefix] } { - dict set successors $prefix {} - } - } - } - - # Identify those prefixes that designate unique values, and those that are - # the full keys - - set uniquePrefixMapping {} - dict for { key valueList } $prefixMapping { - if { [llength $valueList] == 1 } { - dict set uniquePrefixMapping $key [lindex $valueList 0] - } - } - foreach { key value } $data { - dict set uniquePrefixMapping $key $value - } - - # Construct the re. - - return [list \ - [MakeUniquePrefixRegexp $successors $uniquePrefixMapping {}] \ - $uniquePrefixMapping] -} - -#---------------------------------------------------------------------- -# -# MakeUniquePrefixRegexp -- -# -# Service procedure for 'UniquePrefixRegexp' that constructs a regular -# expresison that matches the unique prefixes. -# -# Parameters: -# successors - Dictionary whose keys are all prefixes -# of keys passed to 'UniquePrefixRegexp' and whose -# values are dictionaries whose keys are the characters -# that may follow those prefixes. -# uniquePrefixMapping - Dictionary whose keys are the unique -# prefixes and whose values are not examined. -# prefixString - Current prefix being processed. -# # Results: -# Returns a constructed regular expression that matches the set of -# unique prefixes beginning with the 'prefixString'. +# Returns the system locale. # # Side effects: # None. # #---------------------------------------------------------------------- -proc ::tcl::clock::MakeUniquePrefixRegexp { successors - uniquePrefixMapping - prefixString } { +proc ::tcl::clock::GetSystemLocale {} { + if { $::tcl_platform(platform) ne {windows} } { + # On a non-windows platform, the 'system' locale is the same as + # the 'current' locale - # Get the characters that may follow the current prefix string - - set schars [lsort -ascii [dict keys [dict get $successors $prefixString]]] - if { [llength $schars] == 0 } { - return {} + return [mclocale] } - # If there is more than one successor character, or if the current prefix - # is a unique prefix, surround the generated re with non-capturing - # parentheses. + # On a windows platform, the 'system' locale is adapted from the + # 'current' locale by applying the date and time formats from the + # Control Panel. First, load the 'current' locale if it's not yet + # loaded - set re {} - if { - [dict exists $uniquePrefixMapping $prefixString] - || [llength $schars] > 1 - } then { - append re "(?:" - } - - # Generate a regexp that matches the successors. - - set sep "" - foreach { c } $schars { - set nextPrefix $prefixString$c - regsub -all {[^[:alnum:]]} $c \\\\& rechar - append re $sep $rechar \ - [MakeUniquePrefixRegexp \ - $successors $uniquePrefixMapping $nextPrefix] - set sep | - } + mcpackagelocale set [mclocale] - # If the current prefix is a unique prefix, make all following text - # optional. Otherwise, if there is more than one successor character, - # close the non-capturing parentheses. + # Make a new locale string for the system locale, and get the + # Control Panel information - if { [dict exists $uniquePrefixMapping $prefixString] } { - append re ")?" - } elseif { [llength $schars] > 1 } { - append re ")" + set locale [mclocale]_windows + if { ! [mcpackagelocale present $locale] } { + LoadWindowsDateTimeFormats $locale } - return $re + return $locale } #---------------------------------------------------------------------- # -# MakeParseCodeFromFields -- +# EnterLocale -- # -# Composes Tcl code to extract the Julian Day Number from a dictionary -# containing date fields. +# Switch [mclocale] to a given locale if necessary # # Parameters: -# dateFields -- Dictionary whose keys are fields of the date, -# and whose values are the rightmost positions -# at which those fields appear. -# parseActions -- List of triples: field set, priority, and -# code to emit. Smaller priorities are better, and -# the list must be in ascending order by priority +# locale -- Desired locale # # Results: -# Returns a burst of code that extracts the day number from the given -# date. +# Returns the locale that was previously current. # # Side effects: -# None. +# Does [mclocale]. If necessary, loades the designated locale's files. # #---------------------------------------------------------------------- -proc ::tcl::clock::MakeParseCodeFromFields { dateFields parseActions } { - - set currPrio 999 - set currFieldPos [list] - set currCodeBurst { - error "in ::tcl::clock::MakeParseCodeFromFields: can't happen" - } - - foreach { fieldSet prio parseAction } $parseActions { - # If we've found an answer that's better than any that follow, quit - # now. - - if { $prio > $currPrio } { - break - } - - # Accumulate the field positions that are used in the current field - # grouping. - - set fieldPos [list] - set ok true - foreach field $fieldSet { - if { ! [dict exists $dateFields $field] } { - set ok 0 - break - } - lappend fieldPos [dict get $dateFields $field] - } - - # Quit if we don't have a complete set of fields - if { !$ok } { - continue - } - - # Determine whether the current answer is better than the last. - - set fPos [lsort -integer -decreasing $fieldPos] - - if { $prio == $currPrio } { - foreach currPos $currFieldPos newPos $fPos { - if { - ![string is integer $newPos] - || ![string is integer $currPos] - || $newPos > $currPos - } then { - break - } - if { $newPos < $currPos } { - set ok 0 - break - } - } - } - if { !$ok } { - continue - } - - # Remember the best possibility for extracting date information - - set currPrio $prio - set currFieldPos $fPos - set currCodeBurst $parseAction +proc ::tcl::clock::EnterLocale { locale } { + switch -- $locale system { + set locale [GetSystemLocale] + } current { + set locale [mclocale] } - - return $currCodeBurst + # Select the locale, eventually load it + mcpackagelocale set $locale + return $locale } #---------------------------------------------------------------------- # -# EnterLocale -- -# -# Switch [mclocale] to a given locale if necessary -# -# Parameters: -# locale -- Desired locale -# -# Results: -# Returns the locale that was previously current. +# _hasRegistry -- # -# Side effects: -# Does [mclocale]. If necessary, loads the designated locale's files. +# Helper that checks whether registry module is available (Windows only) +# and loads it on demand. # #---------------------------------------------------------------------- - -proc ::tcl::clock::EnterLocale { locale } { - if { $locale eq {system} } { - if { $::tcl_platform(platform) ne {windows} } { - # On a non-windows platform, the 'system' locale is the same as - # the 'current' locale - - set locale current - } else { - # On a windows platform, the 'system' locale is adapted from the - # 'current' locale by applying the date and time formats from the - # Control Panel. First, load the 'current' locale if it's not yet - # loaded - - mcpackagelocale set [mclocale] - - # Make a new locale string for the system locale, and get the - # Control Panel information - - set locale [mclocale]_windows - if { ! [mcpackagelocale present $locale] } { - LoadWindowsDateTimeFormats $locale - } +proc ::tcl::clock::_hasRegistry {} { + set res 0 + if { $::tcl_platform(platform) eq {windows} } { + if { [catch { package require registry 1.1 }] } { + # try to load registry directly from root (if uninstalled / development env): + if {[regexp {[/\\]library$} [info library]]} {catch { + load [lindex \ + [glob -tails -directory [file dirname [info nameofexecutable]] \ + tclreg*[expr {[::tcl::pkgconfig get debug] ? {g} : {}}].dll] 0 \ + ] registry + }} + } + if { [namespace which -command ::registry] ne "" } { + set res 1 } } - if { $locale eq {current}} { - set locale [mclocale] - } - # Eventually load the locale - mcpackagelocale set $locale + proc ::tcl::clock::_hasRegistry {} [list return $res] + return $res } #---------------------------------------------------------------------- @@ -2363,8 +713,7 @@ proc ::tcl::clock::EnterLocale { locale } { proc ::tcl::clock::LoadWindowsDateTimeFormats { locale } { # Bail out if we can't find the Registry - variable NoRegistry - if { [info exists NoRegistry] } return + if { ![_hasRegistry] } return if { ![catch { registry get "HKEY_CURRENT_USER\\Control Panel\\International" \ @@ -2475,6 +824,8 @@ proc ::tcl::clock::LoadWindowsDateTimeFormats { locale } { # locale -- Current [mclocale] locale, supplied to avoid # an extra call # format -- Format supplied to [clock scan] or [clock format] +# mcd -- Message catalog dictionary for current locale (read-only, +# don't store it to avoid shared references). # # Results: # Returns the string with locale-dependent composite format groups @@ -2485,485 +836,51 @@ proc ::tcl::clock::LoadWindowsDateTimeFormats { locale } { # #---------------------------------------------------------------------- -proc ::tcl::clock::LocalizeFormat { locale format } { +proc ::tcl::clock::LocalizeFormat { locale format mcd } { + variable LocFmtMap - # message catalog key to cache this format - set key FORMAT_$format - - if { [::msgcat::mcexists -exactlocale -exactnamespace $key] } { - return [mc $key] - } - # Handle locale-dependent format groups by mapping them out of the format - # string. Note that the order of the [string map] operations is - # significant because later formats can refer to later ones; for example - # %c can refer to %X, which in turn can refer to %T. - - set list { - %% %% - %D %m/%d/%Y - %+ {%a %b %e %H:%M:%S %Z %Y} - } - lappend list %EY [string map $list [mc LOCALE_YEAR_FORMAT]] - lappend list %T [string map $list [mc TIME_FORMAT_24_SECS]] - lappend list %R [string map $list [mc TIME_FORMAT_24]] - lappend list %r [string map $list [mc TIME_FORMAT_12]] - lappend list %X [string map $list [mc TIME_FORMAT]] - lappend list %EX [string map $list [mc LOCALE_TIME_FORMAT]] - lappend list %x [string map $list [mc DATE_FORMAT]] - lappend list %Ex [string map $list [mc LOCALE_DATE_FORMAT]] - lappend list %c [string map $list [mc DATE_TIME_FORMAT]] - lappend list %Ec [string map $list [mc LOCALE_DATE_TIME_FORMAT]] - set format [string map $list $format] - - ::msgcat::mcset $locale $key $format - return $format -} - -#---------------------------------------------------------------------- -# -# FormatNumericTimeZone -- -# -# Formats a time zone as +hhmmss -# -# Parameters: -# z - Time zone in seconds east of Greenwich -# -# Results: -# Returns the time zone formatted in a numeric form -# -# Side effects: -# None. -# -#---------------------------------------------------------------------- - -proc ::tcl::clock::FormatNumericTimeZone { z } { - if { $z < 0 } { - set z [expr { - $z }] - set retval - + # get map list cached or build it: + if {[dict exists $LocFmtMap $locale]} { + set mlst [dict get $LocFmtMap $locale] } else { - set retval + - } - append retval [::format %02d [expr { $z / 3600 }]] - set z [expr { $z % 3600 }] - append retval [::format %02d [expr { $z / 60 }]] - set z [expr { $z % 60 }] - if { $z != 0 } { - append retval [::format %02d $z] - } - return $retval -} - -#---------------------------------------------------------------------- -# -# FormatStarDate -- -# -# Formats a date as a StarDate. -# -# Parameters: -# date - Dictionary containing 'year', 'dayOfYear', and -# 'localSeconds' fields. -# -# Results: -# Returns the given date formatted as a StarDate. -# -# Side effects: -# None. -# -# Jeff Hobbs put this in to support an atrocious pun about Tcl being -# "Enterprise ready." Now we're stuck with it. -# -#---------------------------------------------------------------------- - -proc ::tcl::clock::FormatStarDate { date } { - variable Roddenberry - - # Get day of year, zero based - - set doy [expr { [dict get $date dayOfYear] - 1 }] - - # Determine whether the year is a leap year - - set lp [IsGregorianLeapYear $date] - - # Convert day of year to a fractional year - - if { $lp } { - set fractYear [expr { 1000 * $doy / 366 }] - } else { - set fractYear [expr { 1000 * $doy / 365 }] - } - - # Put together the StarDate - - return [::format "Stardate %02d%03d.%1d" \ - [expr { [dict get $date year] - $Roddenberry }] \ - $fractYear \ - [expr { [dict get $date localSeconds] % 86400 - / ( 86400 / 10 ) }]] -} - -#---------------------------------------------------------------------- -# -# ParseStarDate -- -# -# Parses a StarDate -# -# Parameters: -# year - Year from the Roddenberry epoch -# fractYear - Fraction of a year specifying the day of year. -# fractDay - Fraction of a day -# -# Results: -# Returns a count of seconds from the Posix epoch. -# -# Side effects: -# None. -# -# Jeff Hobbs put this in to support an atrocious pun about Tcl being -# "Enterprise ready." Now we're stuck with it. -# -#---------------------------------------------------------------------- - -proc ::tcl::clock::ParseStarDate { year fractYear fractDay } { - variable Roddenberry - - # Build a tentative date from year and fraction. - - set date [dict create \ - gregorian 1 \ - era CE \ - year [expr { $year + $Roddenberry }] \ - dayOfYear [expr { $fractYear * 365 / 1000 + 1 }]] - set date [GetJulianDayFromGregorianEraYearDay $date[set date {}]] - - # Determine whether the given year is a leap year - - set lp [IsGregorianLeapYear $date] - - # Reconvert the fractional year according to whether the given year is a - # leap year - - if { $lp } { - dict set date dayOfYear \ - [expr { $fractYear * 366 / 1000 + 1 }] - } else { - dict set date dayOfYear \ - [expr { $fractYear * 365 / 1000 + 1 }] - } - dict unset date julianDay - dict unset date gregorian - set date [GetJulianDayFromGregorianEraYearDay $date[set date {}]] - - return [expr { - 86400 * [dict get $date julianDay] - - 210866803200 - + ( 86400 / 10 ) * $fractDay - }] -} - -#---------------------------------------------------------------------- -# -# ScanWide -- -# -# Scans a wide integer from an input -# -# Parameters: -# str - String containing a decimal wide integer -# -# Results: -# Returns the string as a pure wide integer. Throws an error if the -# string is misformatted or out of range. -# -#---------------------------------------------------------------------- - -proc ::tcl::clock::ScanWide { str } { - set count [::scan $str {%ld %c} result junk] - if { $count != 1 } { - return -code error -errorcode [list CLOCK notAnInteger $str] \ - "\"$str\" is not an integer" - } - if { [incr result 0] != $str } { - return -code error -errorcode [list CLOCK dateTooLarge] \ - "integer value too large to represent" - } - return $result -} - -#---------------------------------------------------------------------- -# -# InterpretTwoDigitYear -- -# -# Given a date that contains only the year of the century, determines -# the target value of a two-digit year. -# -# Parameters: -# date - Dictionary containing fields of the date. -# baseTime - Base time relative to which the date is expressed. -# twoDigitField - Name of the field that stores the two-digit year. -# Default is 'yearOfCentury' -# fourDigitField - Name of the field that will receive the four-digit -# year. Default is 'year' -# -# Results: -# Returns the dictionary augmented with the four-digit year, stored in -# the given key. -# -# Side effects: -# None. -# -# The current rule for interpreting a two-digit year is that the year shall be -# between 1937 and 2037, thus staying within the range of a 32-bit signed -# value for time. This rule may change to a sliding window in future -# versions, so the 'baseTime' parameter (which is currently ignored) is -# provided in the procedure signature. -# -#---------------------------------------------------------------------- - -proc ::tcl::clock::InterpretTwoDigitYear { date baseTime - { twoDigitField yearOfCentury } - { fourDigitField year } } { - set yr [dict get $date $twoDigitField] - if { $yr <= 37 } { - dict set date $fourDigitField [expr { $yr + 2000 }] - } else { - dict set date $fourDigitField [expr { $yr + 1900 }] - } - return $date -} - -#---------------------------------------------------------------------- -# -# AssignBaseYear -- -# -# Places the number of the current year into a dictionary. -# -# Parameters: -# date - Dictionary value to update -# baseTime - Base time from which to extract the year, expressed -# in seconds from the Posix epoch -# timezone - the time zone in which the date is being scanned -# changeover - the Julian Day on which the Gregorian calendar -# was adopted in the target locale. -# -# Results: -# Returns the dictionary with the current year assigned. -# -# Side effects: -# None. -# -#---------------------------------------------------------------------- - -proc ::tcl::clock::AssignBaseYear { date baseTime timezone changeover } { - variable TZData - - # Find the Julian Day Number corresponding to the base time, and - # find the Gregorian year corresponding to that Julian Day. - - set date2 [GetDateFields $baseTime $TZData($timezone) $changeover] - - # Store the converted year - - dict set date era [dict get $date2 era] - dict set date year [dict get $date2 year] - - return $date -} - -#---------------------------------------------------------------------- -# -# AssignBaseIso8601Year -- -# -# Determines the base year in the ISO8601 fiscal calendar. -# -# Parameters: -# date - Dictionary containing the fields of the date that -# is to be augmented with the base year. -# baseTime - Base time expressed in seconds from the Posix epoch. -# timeZone - Target time zone -# changeover - Julian Day of adoption of the Gregorian calendar in -# the target locale. -# -# Results: -# Returns the given date with "iso8601Year" set to the -# base year. -# -# Side effects: -# None. -# -#---------------------------------------------------------------------- - -proc ::tcl::clock::AssignBaseIso8601Year {date baseTime timeZone changeover} { - variable TZData - - # Find the Julian Day Number corresponding to the base time - - set date2 [GetDateFields $baseTime $TZData($timeZone) $changeover] - - # Calculate the ISO8601 date and transfer the year - - dict set date era CE - dict set date iso8601Year [dict get $date2 iso8601Year] - return $date -} - -#---------------------------------------------------------------------- -# -# AssignBaseMonth -- -# -# Places the number of the current year and month into a -# dictionary. -# -# Parameters: -# date - Dictionary value to update -# baseTime - Time from which the year and month are to be -# obtained, expressed in seconds from the Posix epoch. -# timezone - Name of the desired time zone -# changeover - Julian Day on which the Gregorian calendar was adopted. -# -# Results: -# Returns the dictionary with the base year and month assigned. -# -# Side effects: -# None. -# -#---------------------------------------------------------------------- - -proc ::tcl::clock::AssignBaseMonth {date baseTime timezone changeover} { - variable TZData - - # Find the year and month corresponding to the base time - - set date2 [GetDateFields $baseTime $TZData($timezone) $changeover] - dict set date era [dict get $date2 era] - dict set date year [dict get $date2 year] - dict set date month [dict get $date2 month] - return $date -} - -#---------------------------------------------------------------------- -# -# AssignBaseWeek -- -# -# Determines the base year and week in the ISO8601 fiscal calendar. -# -# Parameters: -# date - Dictionary containing the fields of the date that -# is to be augmented with the base year and week. -# baseTime - Base time expressed in seconds from the Posix epoch. -# changeover - Julian Day on which the Gregorian calendar was adopted -# in the target locale. -# -# Results: -# Returns the given date with "iso8601Year" set to the -# base year and "iso8601Week" to the week number. -# -# Side effects: -# None. -# -#---------------------------------------------------------------------- - -proc ::tcl::clock::AssignBaseWeek {date baseTime timeZone changeover} { - variable TZData - - # Find the Julian Day Number corresponding to the base time - - set date2 [GetDateFields $baseTime $TZData($timeZone) $changeover] - - # Calculate the ISO8601 date and transfer the year - - dict set date era CE - dict set date iso8601Year [dict get $date2 iso8601Year] - dict set date iso8601Week [dict get $date2 iso8601Week] - return $date -} - -#---------------------------------------------------------------------- -# -# AssignBaseJulianDay -- -# -# Determines the base day for a time-of-day conversion. -# -# Parameters: -# date - Dictionary that is to get the base day -# baseTime - Base time expressed in seconds from the Posix epoch -# changeover - Julian day on which the Gregorian calendar was -# adpoted in the target locale. -# -# Results: -# Returns the given dictionary augmented with a 'julianDay' field -# that contains the base day. -# -# Side effects: -# None. -# -#---------------------------------------------------------------------- - -proc ::tcl::clock::AssignBaseJulianDay { date baseTime timeZone changeover } { - variable TZData - - # Find the Julian Day Number corresponding to the base time - - set date2 [GetDateFields $baseTime $TZData($timeZone) $changeover] - dict set date julianDay [dict get $date2 julianDay] - - return $date -} - -#---------------------------------------------------------------------- -# -# InterpretHMSP -- -# -# Interprets a time in the form "hh:mm:ss am". -# -# Parameters: -# date -- Dictionary containing "hourAMPM", "minute", "second" -# and "amPmIndicator" fields. -# -# Results: -# Returns the number of seconds from local midnight. -# -# Side effects: -# None. -# -#---------------------------------------------------------------------- - -proc ::tcl::clock::InterpretHMSP { date } { - set hr [dict get $date hourAMPM] - if { $hr == 12 } { - set hr 0 - } - if { [dict get $date amPmIndicator] } { - incr hr 12 - } - dict set date hour $hr - return [InterpretHMS $date[set date {}]] -} - -#---------------------------------------------------------------------- -# -# InterpretHMS -- -# -# Interprets a 24-hour time "hh:mm:ss" -# -# Parameters: -# date -- Dictionary containing the "hour", "minute" and "second" -# fields. -# -# Results: -# Returns the given dictionary augmented with a "secondOfDay" -# field containing the number of seconds from local midnight. -# -# Side effects: -# None. -# -#---------------------------------------------------------------------- - -proc ::tcl::clock::InterpretHMS { date } { - return [expr { - ( [dict get $date hour] * 60 - + [dict get $date minute] ) * 60 - + [dict get $date second] - }] + # Handle locale-dependent format groups by mapping them out of the format + # string. Note that the order of the [string map] operations is + # significant because later formats can refer to later ones; for example + # %c can refer to %X, which in turn can refer to %T. + + set mlst { + %% %% + %D %m/%d/%Y + %+ {%a %b %e %H:%M:%S %Z %Y} + } + lappend mlst %EY [string map $mlst [dict get $mcd LOCALE_YEAR_FORMAT]] + lappend mlst %T [string map $mlst [dict get $mcd TIME_FORMAT_24_SECS]] + lappend mlst %R [string map $mlst [dict get $mcd TIME_FORMAT_24]] + lappend mlst %r [string map $mlst [dict get $mcd TIME_FORMAT_12]] + lappend mlst %X [string map $mlst [dict get $mcd TIME_FORMAT]] + lappend mlst %EX [string map $mlst [dict get $mcd LOCALE_TIME_FORMAT]] + lappend mlst %x [string map $mlst [dict get $mcd DATE_FORMAT]] + lappend mlst %Ex [string map $mlst [dict get $mcd LOCALE_DATE_FORMAT]] + lappend mlst %c [string map $mlst [dict get $mcd DATE_TIME_FORMAT]] + lappend mlst %Ec [string map $mlst [dict get $mcd LOCALE_DATE_TIME_FORMAT]] + + dict set LocFmtMap $locale $mlst + } + + # translate copy of format (don't use format object here, because otherwise + # it can lose its internal representation (string map - convert to unicode) + set locfmt [string map $mlst [string range " $format" 1 end]] + + # Save original format as long as possible, because of internal + # representation (performance). + # Note that in this case such format will be never localized (also + # using another locales). To prevent this return a duplicate (but + # it may be slower). + if {$locfmt eq $format} { + set locfmt $format + } + + return $locfmt } #---------------------------------------------------------------------- @@ -2980,13 +897,12 @@ proc ::tcl::clock::InterpretHMS { date } { # Returns the system time zone. # # Side effects: -# Stores the system time zone in the 'CachedSystemTimeZone' -# variable, since determining it may be an expensive process. +# Stores the system time zone in engine configuration, since +# determining it may be an expensive process. # #---------------------------------------------------------------------- proc ::tcl::clock::GetSystemTimeZone {} { - variable CachedSystemTimeZone variable TimeZoneBad if {[set result [getenv TCL_TZ]] ne {}} { @@ -2994,11 +910,12 @@ proc ::tcl::clock::GetSystemTimeZone {} { } elseif {[set result [getenv TZ]] ne {}} { set timezone $result } else { - # Cache the time zone only if it was detected by one of the - # expensive methods. - if { [info exists CachedSystemTimeZone] } { - set timezone $CachedSystemTimeZone - } elseif { $::tcl_platform(platform) eq {windows} } { + # ask engine for the cached timezone: + set timezone [::tcl::unsupported::clock::configure -system-tz] + if { $timezone ne "" } { + return $timezone + } + if { $::tcl_platform(platform) eq {windows} } { set timezone [GuessWindowsTimeZone] } elseif { [file exists /etc/localtime] && ![catch {ReadZoneinfoFile \ @@ -3007,48 +924,19 @@ proc ::tcl::clock::GetSystemTimeZone {} { } else { set timezone :localtime } - set CachedSystemTimeZone $timezone } if { ![dict exists $TimeZoneBad $timezone] } { - dict set TimeZoneBad $timezone [catch {SetupTimeZone $timezone}] - } - if { [dict get $TimeZoneBad $timezone] } { - return :localtime - } else { - return $timezone + catch {set timezone [SetupTimeZone $timezone]} } -} -#---------------------------------------------------------------------- -# -# ConvertLegacyTimeZone -- -# -# Given an alphanumeric time zone identifier and the system time zone, -# convert the alphanumeric identifier to an unambiguous time zone. -# -# Parameters: -# tzname - Name of the time zone to convert -# -# Results: -# Returns a time zone name corresponding to tzname, but in an -# unambiguous form, generally +hhmm. -# -# This procedure is implemented primarily to allow the parsing of RFC822 -# date/time strings. Processing a time zone name on input is not recommended -# practice, because there is considerable room for ambiguity; for instance, is -# BST Brazilian Standard Time, or British Summer Time? -# -#---------------------------------------------------------------------- + if { [dict exists $TimeZoneBad $timezone] } { + set timezone :localtime + } -proc ::tcl::clock::ConvertLegacyTimeZone { tzname } { - variable LegacyTimeZone + # tell backend - current system timezone: + ::tcl::unsupported::clock::configure -system-tz $timezone - set tzname [string tolower $tzname] - if { ![dict exists $LegacyTimeZone $tzname] } { - return -code error -errorcode [list CLOCK badTZName $tzname] \ - "time zone \"$tzname\" not found" - } - return [dict get $LegacyTimeZone $tzname] + return $timezone } #---------------------------------------------------------------------- @@ -3068,15 +956,19 @@ proc ::tcl::clock::ConvertLegacyTimeZone { tzname } { # #---------------------------------------------------------------------- -proc ::tcl::clock::SetupTimeZone { timezone } { +proc ::tcl::clock::SetupTimeZone { timezone {alias {}} } { variable TZData if {! [info exists TZData($timezone)] } { - variable MINWIDE - if { $timezone eq {:localtime} } { - # Nothing to do, we'll convert using the localtime function - } elseif { + variable TimeZoneBad + if { [dict exists $TimeZoneBad $timezone] } { + return -code error \ + -errorcode [list CLOCK badTimeZone $timezone] \ + "time zone \"$timezone\" not found" + } + variable MINWIDE + if { [regexp {^([-+])(\d\d)(?::?(\d\d)(?::?(\d\d))?)?} $timezone \ -> s hh mm ss] } then { @@ -3109,6 +1001,7 @@ proc ::tcl::clock::SetupTimeZone { timezone } { LoadZoneinfoFile [string range $timezone 1 end] }] } then { + dict set TimeZoneBad $timezone 1 return -code error \ -errorcode [list CLOCK badTimeZone $timezone] \ "time zone \"$timezone\" not found" @@ -3120,25 +1013,43 @@ proc ::tcl::clock::SetupTimeZone { timezone } { if { [lindex [dict get $opts -errorcode] 0] eq {CLOCK} } { dict unset opts -errorinfo } + dict set TimeZoneBad $timezone 1 return -options $opts $data } else { set TZData($timezone) $data } } else { + + variable LegacyTimeZone + # We couldn't parse this as a POSIX time zone. Try again with a # time zone file - this time without a colon if { [catch { LoadTimeZoneFile $timezone }] && [catch { LoadZoneinfoFile $timezone } - opts] } { + + # Check may be a legacy zone: + + if { $alias eq {} && ![catch { + set tzname [dict get $LegacyTimeZone [string tolower $timezone]] + }] } { + set tzname [::tcl::clock::SetupTimeZone $tzname $timezone] + set TZData($timezone) $TZData($tzname) + # tell backend - timezone is initialized and return shared timezone object: + return [::tcl::unsupported::clock::configure -setup-tz $timezone] + } + dict unset opts -errorinfo + dict set TimeZoneBad $timezone 1 return -options $opts "time zone $timezone not found" } set TZData($timezone) $TZData(:$timezone) } } - return + # tell backend - timezone is initialized and return shared timezone object: + ::tcl::unsupported::clock::configure -setup-tz $timezone } #---------------------------------------------------------------------- @@ -3168,10 +1079,9 @@ proc ::tcl::clock::SetupTimeZone { timezone } { proc ::tcl::clock::GuessWindowsTimeZone {} { variable WinZoneInfo - variable NoRegistry variable TimeZoneBad - if { [info exists NoRegistry] } { + if { ![_hasRegistry] } { return :localtime } @@ -3209,12 +1119,12 @@ proc ::tcl::clock::GuessWindowsTimeZone {} { if { [dict exists $WinZoneInfo $data] } { set tzname [dict get $WinZoneInfo $data] if { ! [dict exists $TimeZoneBad $tzname] } { - dict set TimeZoneBad $tzname [catch {SetupTimeZone $tzname}] + catch {set tzname [SetupTimeZone $tzname]} } } else { set tzname {} } - if { $tzname eq {} || [dict get $TimeZoneBad $tzname] } { + if { $tzname eq {} || [dict exists $TimeZoneBad $tzname] } { lassign $data \ bias stdBias dstBias \ stdYear stdMonth stdDayOfWeek stdDayOfMonth \ @@ -3885,14 +1795,14 @@ proc ::tcl::clock::DeterminePosixDSTTime { z bound y } { # Determine the start or end day of DST - set date [dict create era CE year $y] + set date [dict create era CE year $y gregorian 1] set doy [dict get $z ${bound}DayOfYear] if { $doy ne {} } { # Time was specified as a day of the year if { [dict get $z ${bound}J] ne {} - && [IsGregorianLeapYear $y] + && [IsGregorianLeapYear $date] && ( $doy > $FEB_28 ) } { incr doy } @@ -3941,43 +1851,6 @@ proc ::tcl::clock::DeterminePosixDSTTime { z bound y } { #---------------------------------------------------------------------- # -# GetLocaleEra -- -# -# Given local time expressed in seconds from the Posix epoch, -# determine localized era and year within the era. -# -# Parameters: -# date - Dictionary that must contain the keys, 'localSeconds', -# whose value is expressed as the appropriate local time; -# and 'year', whose value is the Gregorian year. -# etable - Value of the LOCALE_ERAS key in the message catalogue -# for the target locale. -# -# Results: -# Returns the dictionary, augmented with the keys, 'localeEra' and -# 'localeYear'. -# -#---------------------------------------------------------------------- - -proc ::tcl::clock::GetLocaleEra { date etable } { - set index [BSearch $etable [dict get $date localSeconds]] - if { $index < 0} { - dict set date localeEra \ - [::format %02d [expr { [dict get $date year] / 100 }]] - dict set date localeYear [expr { - [dict get $date year] % 100 - }] - } else { - dict set date localeEra [lindex $etable $index 1] - dict set date localeYear [expr { - [dict get $date year] - [lindex $etable $index 2] - }] - } - return $date -} - -#---------------------------------------------------------------------- -# # GetJulianDayFromEraYearDay -- # # Given a year, month and day on the Gregorian calendar, determines @@ -4155,333 +2028,6 @@ proc ::tcl::clock::WeekdayOnOrBefore { weekday j } { #---------------------------------------------------------------------- # -# BSearch -- -# -# Service procedure that does binary search in several places inside the -# 'clock' command. -# -# Parameters: -# list - List of lists, sorted in ascending order by the -# first elements -# key - Value to search for -# -# Results: -# Returns the index of the greatest element in $list that is less than -# or equal to $key. -# -# Side effects: -# None. -# -#---------------------------------------------------------------------- - -proc ::tcl::clock::BSearch { list key } { - if {[llength $list] == 0} { - return -1 - } - if { $key < [lindex $list 0 0] } { - return -1 - } - - set l 0 - set u [expr { [llength $list] - 1 }] - - while { $l < $u } { - # At this point, we know that - # $k >= [lindex $list $l 0] - # Either $u == [llength $list] or else $k < [lindex $list $u+1 0] - # We find the midpoint of the interval {l,u} rounded UP, compare - # against it, and set l or u to maintain the invariant. Note that the - # interval shrinks at each step, guaranteeing convergence. - - set m [expr { ( $l + $u + 1 ) / 2 }] - if { $key >= [lindex $list $m 0] } { - set l $m - } else { - set u [expr { $m - 1 }] - } - } - - return $l -} - -#---------------------------------------------------------------------- -# -# clock add -- -# -# Adds an offset to a given time. -# -# 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 -# (Deprecated) 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 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. -# -#---------------------------------------------------------------------- - -proc ::tcl::clock::add { clockval args } { - if { [llength $args] % 2 != 0 } { - set cmdName "clock add" - return -code error \ - -errorcode [list CLOCK wrongNumArgs] \ - "wrong \# args: should be\ - \"$cmdName clockval ?number units?...\ - ?-gmt boolean? ?-locale LOCALE? ?-timezone ZONE?\"" - } - if { [catch { expr {wide($clockval)} } result] } { - return -code error $result - } - - set offsets {} - set gmt 0 - set locale c - set timezone [GetSystemTimeZone] - - foreach { a b } $args { - if { [string is integer -strict $a] } { - lappend offsets $a $b - } else { - switch -exact -- $a { - -g - -gm - -gmt { - set saw(-gmt) {} - set gmt $b - } - -l - -lo - -loc - -loca - -local - -locale { - set locale [string tolower $b] - } - -t - -ti - -tim - -time - -timez - -timezo - -timezon - - -timezone { - set saw(-timezone) {} - set timezone $b - } - default { - throw [list CLOCK badOption $a] \ - "bad option \"$a\":\ - must be -gmt, -locale or -timezone" - } - } - } - } - - # Check options for validity - - if { [info exists saw(-gmt)] && [info exists saw(-timezone)] } { - return -code error \ - -errorcode [list CLOCK gmtWithTimezone] \ - "cannot use -gmt and -timezone in same call" - } - if { [catch { expr { wide($clockval) } } result] } { - return -code error "expected integer but got \"$clockval\"" - } - if { ![string is boolean -strict $gmt] } { - return -code error "expected boolean value but got \"$gmt\"" - } elseif { $gmt } { - set timezone :GMT - } - - EnterLocale $locale - - set changeover [mc GREGORIAN_CHANGE_DATE] - - if {[catch {SetupTimeZone $timezone} retval opts]} { - dict unset opts -errorinfo - return -options $opts $retval - } - - try { - foreach { quantity unit } $offsets { - switch -exact -- $unit { - years - year { - set clockval [AddMonths [expr { 12 * $quantity }] \ - $clockval $timezone $changeover] - } - months - month { - set clockval [AddMonths $quantity $clockval $timezone \ - $changeover] - } - - weeks - week { - set clockval [AddDays [expr { 7 * $quantity }] \ - $clockval $timezone $changeover] - } - days - day { - set clockval [AddDays $quantity $clockval $timezone \ - $changeover] - } - - hours - hour { - set clockval [expr { 3600 * $quantity + $clockval }] - } - minutes - minute { - set clockval [expr { 60 * $quantity + $clockval }] - } - seconds - second { - set clockval [expr { $quantity + $clockval }] - } - - default { - throw [list CLOCK badUnit $unit] \ - "unknown unit \"$unit\", must be \ - years, months, weeks, days, hours, minutes or seconds" - } - } - } - return $clockval - } trap CLOCK {result opts} { - # Conceal the innards of [clock] when it's an expected error - dict unset opts -errorinfo - return -options $opts $result - } -} - -#---------------------------------------------------------------------- -# -# AddMonths -- -# -# Add a given number of months to a given clock value in a given -# time zone. -# -# Parameters: -# months - Number of months to add (may be negative) -# clockval - Seconds since the epoch before the operation -# timezone - Time zone in which the operation is to be performed -# -# Results: -# Returns the new clock value as a number of seconds since -# the epoch. -# -# Side effects: -# None. -# -#---------------------------------------------------------------------- - -proc ::tcl::clock::AddMonths { months clockval timezone changeover } { - variable DaysInRomanMonthInCommonYear - variable DaysInRomanMonthInLeapYear - variable TZData - - # Convert the time to year, month, day, and fraction of day. - - set date [GetDateFields $clockval $TZData($timezone) $changeover] - dict set date secondOfDay [expr { - [dict get $date localSeconds] % 86400 - }] - dict set date tzName $timezone - - # Add the requisite number of months - - set m [dict get $date month] - incr m $months - incr m -1 - set delta [expr { $m / 12 }] - set mm [expr { $m % 12 }] - dict set date month [expr { $mm + 1 }] - dict incr date year $delta - - # If the date doesn't exist in the current month, repair it - - if { [IsGregorianLeapYear $date] } { - set hath [lindex $DaysInRomanMonthInLeapYear $mm] - } else { - set hath [lindex $DaysInRomanMonthInCommonYear $mm] - } - if { [dict get $date dayOfMonth] > $hath } { - dict set date dayOfMonth $hath - } - - # Reconvert to a number of seconds - - set date [GetJulianDayFromEraYearMonthDay \ - $date[set date {}]\ - $changeover] - dict set date localSeconds [expr { - -210866803200 - + ( 86400 * wide([dict get $date julianDay]) ) - + [dict get $date secondOfDay] - }] - set date [ConvertLocalToUTC $date[set date {}] $TZData($timezone) \ - $changeover] - - return [dict get $date seconds] - -} - -#---------------------------------------------------------------------- -# -# AddDays -- -# -# Add a given number of days to a given clock value in a given time -# zone. -# -# Parameters: -# days - Number of days to add (may be negative) -# clockval - Seconds since the epoch before the operation -# timezone - Time zone in which the operation is to be performed -# changeover - Julian Day on which the Gregorian calendar was adopted -# in the target locale. -# -# Results: -# Returns the new clock value as a number of seconds since the epoch. -# -# Side effects: -# None. -# -#---------------------------------------------------------------------- - -proc ::tcl::clock::AddDays { days clockval timezone changeover } { - variable TZData - - # Convert the time to Julian Day - - set date [GetDateFields $clockval $TZData($timezone) $changeover] - dict set date secondOfDay [expr { - [dict get $date localSeconds] % 86400 - }] - dict set date tzName $timezone - - # Add the requisite number of days - - dict incr date julianDay $days - - # Reconvert to a number of seconds - - dict set date localSeconds [expr { - -210866803200 - + ( 86400 * wide([dict get $date julianDay]) ) - + [dict get $date secondOfDay] - }] - set date [ConvertLocalToUTC $date[set date {}] $TZData($timezone) \ - $changeover] - - return [dict get $date seconds] - -} - -#---------------------------------------------------------------------- -# # ChangeCurrentLocale -- # # The global locale was changed within msgcat. @@ -4499,20 +2045,7 @@ proc ::tcl::clock::AddDays { days clockval timezone changeover } { #---------------------------------------------------------------------- proc ::tcl::clock::ChangeCurrentLocale {args} { - variable FormatProc - variable LocaleNumeralCache - variable CachedSystemTimeZone - variable TimeZoneBad - - foreach p [info procs [namespace current]::scanproc'*'current] { - rename $p {} - } - foreach p [info procs [namespace current]::formatproc'*'current] { - rename $p {} - } - - catch {array unset FormatProc *'current} - set LocaleNumeralCache {} + ::tcl::unsupported::clock::configure -current-locale [lindex $args 0] } #---------------------------------------------------------------------- @@ -4533,21 +2066,17 @@ proc ::tcl::clock::ChangeCurrentLocale {args} { #---------------------------------------------------------------------- proc ::tcl::clock::ClearCaches {} { - variable FormatProc - variable LocaleNumeralCache - variable CachedSystemTimeZone + variable LocFmtMap + variable mcMergedCat variable TimeZoneBad - foreach p [info procs [namespace current]::scanproc'*] { - rename $p {} - } - foreach p [info procs [namespace current]::formatproc'*] { - rename $p {} - } + # tell backend - should invalidate: + ::tcl::unsupported::clock::configure -clear + + # clear msgcat cache: + set mcMergedCat [dict create] - catch {unset FormatProc} - set LocaleNumeralCache {} - catch {unset CachedSystemTimeZone} + set LocFmtMap {} set TimeZoneBad {} InitTZData } diff --git a/library/init.tcl b/library/init.tcl index 6500d8e..1209619 100644 --- a/library/init.tcl +++ b/library/init.tcl @@ -109,22 +109,17 @@ if {[interp issafe]} { # Set up the 'clock' ensemble - namespace eval ::tcl::clock [list variable TclLibDir $::tcl_library] - - proc ::tcl::initClock {} { - # Auto-loading stubs for 'clock.tcl' - - foreach cmd {add format scan} { - proc ::tcl::clock::$cmd args { - variable TclLibDir - source [file join $TclLibDir clock.tcl] - return [uplevel 1 [info level 0]] - } + proc clock args { + set cmdmap [dict create] + foreach cmd {add clicks format microseconds milliseconds scan seconds} { + dict set cmdmap $cmd ::tcl::clock::$cmd } - - rename ::tcl::initClock {} + namespace inscope ::tcl::clock [list namespace ensemble create -command \ + [uplevel 1 [list ::namespace origin [::lindex [info level 0] 0]]] \ + -map $cmdmap] + ::tcl::unsupported::clock::configure -init-complete + uplevel 1 [info level 0] } - ::tcl::initClock } # Conditionalize for presence of exec. diff --git a/library/tclIndex b/library/tclIndex index 04f6d41..2d4a957 100644 --- a/library/tclIndex +++ b/library/tclIndex @@ -19,6 +19,29 @@ set auto_index(::auto_mkindex_parser::command) [list ::tcl::Pkg::source [file jo set auto_index(::auto_mkindex_parser::commandInit) [list ::tcl::Pkg::source [file join $dir auto.tcl]] set auto_index(::auto_mkindex_parser::fullname) [list ::tcl::Pkg::source [file join $dir auto.tcl]] set auto_index(::auto_mkindex_parser::indexEntry) [list ::tcl::Pkg::source [file join $dir auto.tcl]] +set auto_index(::tcl::clock::Initialize) [list ::tcl::Pkg::source [file join $dir clock.tcl]] +set auto_index(::tcl::clock::mcget) [list ::tcl::Pkg::source [file join $dir clock.tcl]] +set auto_index(::tcl::clock::mcMerge) [list ::tcl::Pkg::source [file join $dir clock.tcl]] +set auto_index(::tcl::clock::GetSystemLocale) [list ::tcl::Pkg::source [file join $dir clock.tcl]] +set auto_index(::tcl::clock::EnterLocale) [list ::tcl::Pkg::source [file join $dir clock.tcl]] +set auto_index(::tcl::clock::_hasRegistry) [list ::tcl::Pkg::source [file join $dir clock.tcl]] +set auto_index(::tcl::clock::LoadWindowsDateTimeFormats) [list ::tcl::Pkg::source [file join $dir clock.tcl]] +set auto_index(::tcl::clock::LocalizeFormat) [list ::tcl::Pkg::source [file join $dir clock.tcl]] +set auto_index(::tcl::clock::GetSystemTimeZone) [list ::tcl::Pkg::source [file join $dir clock.tcl]] +set auto_index(::tcl::clock::SetupTimeZone) [list ::tcl::Pkg::source [file join $dir clock.tcl]] +set auto_index(::tcl::clock::GuessWindowsTimeZone) [list ::tcl::Pkg::source [file join $dir clock.tcl]] +set auto_index(::tcl::clock::LoadTimeZoneFile) [list ::tcl::Pkg::source [file join $dir clock.tcl]] +set auto_index(::tcl::clock::LoadZoneinfoFile) [list ::tcl::Pkg::source [file join $dir clock.tcl]] +set auto_index(::tcl::clock::ReadZoneinfoFile) [list ::tcl::Pkg::source [file join $dir clock.tcl]] +set auto_index(::tcl::clock::ParsePosixTimeZone) [list ::tcl::Pkg::source [file join $dir clock.tcl]] +set auto_index(::tcl::clock::ProcessPosixTimeZone) [list ::tcl::Pkg::source [file join $dir clock.tcl]] +set auto_index(::tcl::clock::DeterminePosixDSTTime) [list ::tcl::Pkg::source [file join $dir clock.tcl]] +set auto_index(::tcl::clock::GetJulianDayFromEraYearDay) [list ::tcl::Pkg::source [file join $dir clock.tcl]] +set auto_index(::tcl::clock::GetJulianDayFromEraYearMonthWeekDay) [list ::tcl::Pkg::source [file join $dir clock.tcl]] +set auto_index(::tcl::clock::IsGregorianLeapYear) [list ::tcl::Pkg::source [file join $dir clock.tcl]] +set auto_index(::tcl::clock::WeekdayOnOrBefore) [list ::tcl::Pkg::source [file join $dir clock.tcl]] +set auto_index(::tcl::clock::ChangeCurrentLocale) [list ::tcl::Pkg::source [file join $dir clock.tcl]] +set auto_index(::tcl::clock::ClearCaches) [list ::tcl::Pkg::source [file join $dir clock.tcl]] set auto_index(foreachLine) [list ::tcl::Pkg::source [file join $dir foreachline.tcl]] set auto_index(::tcl::history) [list ::tcl::Pkg::source [file join $dir history.tcl]] set auto_index(history) [list ::tcl::Pkg::source [file join $dir history.tcl]] diff --git a/tests-perf/clock.perf.tcl b/tests-perf/clock.perf.tcl index ad928c2..b55c851 100644 --- a/tests-perf/clock.perf.tcl +++ b/tests-perf/clock.perf.tcl @@ -356,10 +356,16 @@ proc test-other {{reptime 1000}} { # Scan : julian day (overflow) {catch {clock scan 5373485 -format %J}} + setup {set _(org-reptime) $_(reptime); lset _(reptime) 1 50} + # Scan : test rotate of GC objects (format is dynamic, so tcl-obj removed with last reference) - {set i 0; time { clock scan "[incr i] - 25.11.2015" -format "$i - %d.%m.%Y" -base 0 -gmt 1 } 50} + setup {set i -1} + {clock scan "[incr i] - 25.11.2015" -format "$i - %d.%m.%Y" -base 0 -gmt 1} # Scan : test reusability of GC objects (format is dynamic, so tcl-obj removed with last reference) - {set i 50; time { clock scan "[incr i -1] - 25.11.2015" -format "$i - %d.%m.%Y" -base 0 -gmt 1 } 50} + setup {incr i; set j $i} + {clock scan "[incr j -1] - 25.11.2015" -format "$j - %d.%m.%Y" -base 0 -gmt 1} + setup {set _(reptime) $_(org-reptime); set j $i} + {clock scan "[incr j -1] - 25.11.2015" -format "$j - %d.%m.%Y" -base 0 -gmt 1; if {!$j} {set j $i}} } } diff --git a/tests/clock-ivm.test b/tests/clock-ivm.test new file mode 100644 index 0000000..acf4adb --- /dev/null +++ b/tests/clock-ivm.test @@ -0,0 +1,8 @@ +# clock-ivm.test -- +# +# This test file covers the 'clock' command using inverted validity mode. +# +# See the file "clock.test" for more information. + +::tcl::unsupported::clock::configure -valid [expr {![::tcl::unsupported::clock::configure -valid]}] +source [file join [file dirname [info script]] clock.test]
\ No newline at end of file diff --git a/tests/clock.test b/tests/clock.test index 1db5af8..b809ba1 100644 --- a/tests/clock.test +++ b/tests/clock.test @@ -7,6 +7,7 @@ # generates output for errors. No output means no errors were found. # # Copyright © 2004 Kevin B. Kenny. All rights reserved. +# 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. @@ -19,9 +20,8 @@ if {"::tcltest" ni [namespace children]} { if {[testConstraint win]} { if {[catch { ::tcltest::loadTestedCommands - package require registry }]} { - namespace eval ::tcl::clock {variable NoRegistry {}} + # nothing to be done (registry loaded on demand) } } @@ -32,8 +32,29 @@ testConstraint detroit \ testConstraint y2038 \ [expr {[clock format 2158894800 -format %z -timezone :America/Detroit] eq {-0400}}] +# Test with both validity modes - validate on / off: + +set valid_mode [::tcl::unsupported::clock::configure -valid] + +# Wrapper to show validity mode in the test-case name (for possible errors): +proc test {args} { + variable valid_mode + lset args 0 [lindex $args 0].vm:$valid_mode + tailcall ::tcltest::test {*}$args +} + +puts [outputChannel] " Validity default mode: [expr {$valid_mode ? "on": "off"}]" +testConstraint valid_off [expr {![::tcl::unsupported::clock::configure -valid]}] + +if {[namespace which -command ::tcl::unsupported::timerate] ne ""} { + namespace import ::tcl::unsupported::timerate +} + # TEST PLAN +# clock-0: +# several base test-cases +# # clock-1: # [clock format] - tests of bad and empty arguments # @@ -250,11 +271,62 @@ proc ::testClock::registry { cmd path key } { return [dict get $reg $path $key] } +# Base test cases: + +test clock-0.1 "initial: auto-loading of ensemble and stubs on demand" -setup { + set i [interp create]; # because clock can be used somewhere, test it in new interp: +} -body { + $i eval { + lappend ret ens:[namespace ensemble exists ::clock] + clock seconds; # init ensemble (but not yet stubs, loading of clock.tcl retarded) + lappend ret ens:[namespace ensemble exists ::clock] + lappend ret stubs:[expr {[namespace which -command ::tcl::clock::GetSystemTimeZone] ne ""}] + clock format -now; # clock.tcl stubs expected + lappend ret stubs:[expr {[namespace which -command ::tcl::clock::GetSystemTimeZone] ne ""}] + } +} -cleanup { + interp delete $i +} -result {ens:0 ens:1 stubs:0 stubs:1} +test clock-0.1a "initial: safe interpreter shares clock command with parent" -setup { + set i [interp create] + $i eval {set sci [interp create -safe]} +} -body { + $i eval { + lappend ret ens:[namespace ensemble exists ::clock] + $sci eval { clock seconds }; # init ensemble (but not yet stubs, loading of clock.tcl retarded) + lappend ret ens:[namespace ensemble exists ::clock] + lappend ret stubs:[expr {[namespace which -command ::tcl::clock::GetSystemTimeZone] ne ""}] + $sci eval { clock format -now }; # clock.tcl stubs expected + lappend ret stubs:[expr {[namespace which -command ::tcl::clock::GetSystemTimeZone] ne ""}] + } +} -cleanup { + interp delete $i +} -result {ens:0 ens:1 stubs:0 stubs:1} + +test clock-0.2 "initial: loading of format/locale does not overwrite interp state (errorInfo)" -setup { + # be sure - we have no cached locale/msgcat, etc: + if {[namespace which -command ::tcl::clock::ClearCaches] ne ""} { + ::tcl::clock::ClearCaches + } +} -body { + if {[catch { + return -level 0 -code error -errorcode {EXPERR TEST-ERROR} -errorinfo "ERROR expected error" test + }]} { + clock format -now -locale de; # should not overwrite error code/info + list $::errorCode $::errorInfo + } +} -result {{EXPERR TEST-ERROR} {ERROR expected error}} + # Test some of the basics of [clock format] +set syntax "clockval|-now ?-format string? ?-gmt boolean? ?-locale LOCALE? ?-timezone ZONE?" test clock-1.0 "clock format - wrong # args" { list [catch {clock format} msg] $msg $::errorCode -} {1 {wrong # args: should be "clock format clockval ?-format string? ?-gmt boolean? ?-locale LOCALE? ?-timezone ZONE?"} {CLOCK wrongNumArgs}} +} [subst {1 {wrong # args: should be "clock format $syntax"} {CLOCK wrongNumArgs}}] + +test clock-1.0.1 "clock format - wrong # args (compiled ensemble with invalid syntax)" { + list [catch {clock format 0 -too-few-options-4-test} msg] $msg $::errorCode +} [subst {1 {wrong # args: should be "clock format $syntax"} {CLOCK wrongNumArgs}}] test clock-1.1 "clock format - bad time" { list [catch {clock format foo} msg] $msg @@ -268,13 +340,14 @@ test clock-1.3 "clock format - empty val" { clock format 0 -gmt 1 -format "" } {} -test clock-1.4 "clock format - bad flag" {*}{ - -body { +test clock-1.4 "clock format - bad flag" { + # range error message for possible extensions: list [catch {clock format 0 -oops badflag} msg] $msg $::errorCode - } - -match glob - -result {1 {bad option "-oops": must be -format, -gmt, -locale, or -timezone} {CLOCK badOption -oops}} -} +} [subst {1 {bad option "-oops": must be -format, -gmt, -locale, or -timezone} {CLOCK badOption -oops}}] +test clock-1.4.1 "clock format - unexpected option for this sub-command" { + # range error message for possible extensions: + list [catch {clock format 0 -base 0} msg] $msg $::errorCode +} [subst {1 {bad option "-base": must be -format, -gmt, -locale, or -timezone} {CLOCK badOption -base}}] test clock-1.5 "clock format - bad timezone" { list [catch {clock format 0 -format "%s" -timezone :NOWHERE} msg] $msg $::errorCode @@ -288,6 +361,20 @@ test clock-1.7 "clock format - option abbreviations" { clock format 0 -g true -f "%Y-%m-%d" } 1970-01-01 +test clock-1.7.1 "clock format - command abbreviations (compat regression test)" { + clock f 0 -g 1 -f "%Y-%m-%d" +} 1970-01-01 + +test clock-1.8 "clock format -now" { + # give one second more for test (if on boundary of the current second): + set n [clock format [clock seconds] -g 1 -f "%s"] + expr {[clock format -now -g 1 -f "%s"] in [list $n [incr n]]} +} 1 + +test clock-1.9 "clock arguments: option doubly present" { + list [catch {clock format 0 -gmt 1 -gmt 0} result] $result +} {1 {bad option "-gmt": doubly present}} + # BEGIN testcases2 # Test formatting of Gregorian year, month, day, all formats @@ -15291,6 +15378,84 @@ test clock-4.96 { format time of day 23:59:59 } { -locale en_US_roman \ -gmt true } {23 xxiii 11 xi 23 xxiii 11 xi 59 lix PM pm 11:59:59 pm 23:59 59 lix 23:59:59 23:59:59 xxiii h lix m lix s Thu Jan 1 23:59:59 GMT 1970} + +test clock-4.97.1 { format JDN/JD (calendar and astronomical) } { + clock format 0 -format {%J %EJ %Ej} -gmt true +} {2440588 2440588.0 2440587.5} +test clock-4.97.2 { format JDN/JD (calendar and astronomical) } { + clock format 43200 -format {%J %EJ %Ej} -gmt true +} {2440588 2440588.5 2440588.0} +test clock-4.97.3 { format JDN/JD (calendar and astronomical) } { + clock format 86399 -format {%J %EJ %Ej} -gmt true +} {2440588 2440588.99998843 2440588.49998843} +test clock-4.97.4 { format JDN/JD (calendar and astronomical) } { + clock format 86400 -format {%J %EJ %Ej} -gmt true +} {2440589 2440589.0 2440588.5} +test clock-4.97.5 { format JDN/JD (calendar and astronomical) } { + clock format 129599 -format {%J %EJ %Ej} -gmt true +} {2440589 2440589.49998843 2440588.99998843} +test clock-4.97.6 { format JDN/JD (calendar and astronomical) } { + clock format 129600 -format {%J %EJ %Ej} -gmt true +} {2440589 2440589.5 2440589.0} +test clock-4.97.7 { format JDN/JD (calendar and astronomical) } { + set i 1548249092 + list \ + [clock format $i -format {%J %EJ %Ej} -gmt true] \ + [clock format [incr i] -format {%J %EJ %Ej} -gmt true] \ + [clock format [incr i] -format {%J %EJ %Ej} -gmt true] +} {{2458507 2458507.54967593 2458507.04967593} {2458507 2458507.5496875 2458507.0496875} {2458507 2458507.54969907 2458507.04969907}} +test clock-4.97.8 { format JDN/JD (calendar and astronomical) } { + set res {} + foreach i { + -172800 -129600 -86400 -43200 + -1 0 1 21600 43199 43200 86399 + 86400 86401 108000 129600 172800 + } { + lappend res $i [clock format [expr {-210866803200 - $i}] \ + -format {%EE %Y-%m-%d %T -- %J %EJ %Ej} -gmt true] + } + set res +} [list \ + -172800 {B.C.E. 4713-01-03 00:00:00 -- 0000002 2.0 1.5} \ + -129600 {B.C.E. 4713-01-02 12:00:00 -- 0000001 1.5 1.0} \ + -86400 {B.C.E. 4713-01-02 00:00:00 -- 0000001 1.0 0.5} \ + -43200 {B.C.E. 4713-01-01 12:00:00 -- 0000000 0.5 0.0} \ + -1 {B.C.E. 4713-01-01 00:00:01 -- 0000000 0.00001157 -0.49998843} \ + 0 {B.C.E. 4713-01-01 00:00:00 -- 0000000 0.0 -0.5} \ + 1 {B.C.E. 4714-12-31 23:59:59 -- -000001 -0.00001157 -0.50001157} \ + 21600 {B.C.E. 4714-12-31 18:00:00 -- -000001 -0.25 -0.75} \ + 43199 {B.C.E. 4714-12-31 12:00:01 -- -000001 -0.49998843 -0.99998843} \ + 43200 {B.C.E. 4714-12-31 12:00:00 -- -000001 -0.5 -1.0} \ + 86399 {B.C.E. 4714-12-31 00:00:01 -- -000001 -0.99998843 -1.49998843} \ + 86400 {B.C.E. 4714-12-31 00:00:00 -- -000001 -1.0 -1.5} \ + 86401 {B.C.E. 4714-12-30 23:59:59 -- -000002 -1.00001157 -1.50001157} \ + 108000 {B.C.E. 4714-12-30 18:00:00 -- -000002 -1.25 -1.75} \ + 129600 {B.C.E. 4714-12-30 12:00:00 -- -000002 -1.5 -2.0} \ + 172800 {B.C.E. 4714-12-30 00:00:00 -- -000002 -2.0 -2.5} \ +] +test clock-4.97.9 { format JDN/JD (calendar and astronomical) } { + set res {} + foreach i { + -86400 -43200 + -1 0 1 + 43199 43200 43201 86400 + } { + lappend res $i [clock format [expr {653133196800 + $i}] \ + -format {%Y-%m-%d %T -- %J %EJ %Ej} -gmt true] + } + set res +} [list \ + -86400 {22666-12-19 00:00:00 -- 9999999 9999999.0 9999998.5} \ + -43200 {22666-12-19 12:00:00 -- 9999999 9999999.5 9999999.0} \ + -1 {22666-12-19 23:59:59 -- 9999999 9999999.99998843 9999999.49998843} \ + 0 {22666-12-20 00:00:00 -- 10000000 10000000.0 9999999.5} \ + 1 {22666-12-20 00:00:01 -- 10000000 10000000.00001157 9999999.50001157} \ + 43199 {22666-12-20 11:59:59 -- 10000000 10000000.49998843 9999999.99998843} \ + 43200 {22666-12-20 12:00:00 -- 10000000 10000000.5 10000000.0} \ + 43201 {22666-12-20 12:00:01 -- 10000000 10000000.50001157 10000000.00001157} \ + 86400 {22666-12-21 00:00:00 -- 10000001 10000001.0 10000000.5} \ +] + # END testcases4 # BEGIN testcases5 @@ -18539,24 +18704,223 @@ test clock-6.10 {input of seconds - overflow} { } {1 {integer value too large to represent} {CLOCK dateTooLarge}} foreach sign {{} -} { - test clock-6.10a$sign {input of seconds - overflow, bug [1f40aa83c5]} { + test clock-6.10a {input of seconds - overflow, bug [1f40aa83c5]} { list [catch {clock scan ${sign}27670116110564327423 -format %s -gmt true} result opt] $result [dict getd $opt -errorcode ""] } {1 {integer value too large to represent} {CLOCK dateTooLarge}} - test clock-6.10b$sign {input of seconds - overflow, bug [1f40aa83c5]} { + test clock-6.10b {input of seconds - overflow, bug [1f40aa83c5]} { list [catch {clock scan ${sign}27670116110564327424 -format %s -gmt true} result opt] $result [dict getd $opt -errorcode ""] } {1 {integer value too large to represent} {CLOCK dateTooLarge}} + test clock-6.10c {input of seconds - no overflow, bug [1f40aa83c5]} { + list [catch {clock scan ${sign}[string repeat 9 18] -format %s -gmt true} result opt] $result [dict getd $opt -errorcode ""] + } [list 0 ${sign}[string repeat 9 18] {}] + test clock-6.10d {input of seconds - overflow, bug [1f40aa83c5]} { + list [catch {clock scan ${sign}[string repeat 9 19] -format %s -gmt true} result opt] $result [dict getd $opt -errorcode ""] + } {1 {integer value too large to represent} {CLOCK dateTooLarge}} + # both fololowing freescan test don't generate overflow error, + # since it is a free scan, thus the token is simply not recognized further in yacc lexer, + # therefore we get parse error (can be surely changed latter): + test clock-6.10e {input of seconds - overflow (but since freescan parse error, but not boom), bug [1f40aa83c5]} -body { + list [catch {clock scan ${sign}27670116110564327423 -gmt true} result opt] $result [dict getd $opt -errorcode ""] + } -match glob -result {1 {unable to convert date-time string "*": syntax error *} {TCL VALUE DATE PARSE}} + test clock-6.10f {input of seconds - overflow (but since freescan parse error, but not boom), bug [1f40aa83c5]} -body { + list [catch {clock scan ${sign}27670116110564327424 -gmt true} result opt] $result [dict getd $opt -errorcode ""] + } -match glob -result {1 {unable to convert date-time string "*": syntax error *} {TCL VALUE DATE PARSE}} }; unset sign -test clock-6.10c {input of seconds - overflow ??, bug [1f40aa83c5]} knownBug { - clock scan 27670116110564327423 -gmt true -} 89170590268800 -test clock-6.10d {input of seconds - overflow ??, bug [1f40aa83c5]} knownBug { - clock scan 27670116110564327424 -gmt true -} -90247104115200 test clock-6.11 {input of seconds - two values} { clock scan {1 2} -format {%s %s} -gmt true } 2 +test clock-6.12.0 {input of short forms of locale token (%b)} { + list [clock scan "12 Ja 2001" -format "%d %b %Y" -locale en_US_roman -gmt 1] \ + [clock scan "12 Au 2001" -format "%d %b %Y" -locale en_US_roman -gmt 1] +} {979257600 997574400} +test clock-6.12.1 {input of all forms of unambiguous short locale token (%b)} { + # find all unambiguous short forms and check it'll be scanned successful and correctly: + set months {January February March April May June July August September October November December} + set res {} + foreach mon $months { + set i 0 + while {[incr i] < [string length $mon]} { + # short month form: + set shm [string range $mon 0 $i] + # differentiate ambiguous: + if {[llength [lsearch -all -glob $months "${shm}*"]] <= 1} { + # unambiguous (expected date with wull month): + set e "12 $mon 2001" + } else { + # ambiguous (expected error): + set e "input string does not match supplied format" + } + set s "12 $shm 2001" + # scan and format with full month name: + catch {clock format \ + [clock scan $s -format "%d %b %Y" -locale en_US_roman -gmt 1] \ + -format "%d %B %Y" -locale en_US_roman -gmt 1} t + # check it corresponds the full form: + if {$t ne $e} { + lappend res "unexpected result converting $s, expected \"$e\", got \"$t\"" + } + } + } + set res +} {} +test clock-6.13 {input of lowercase locale token (%b)} { + list [clock scan "12 ja 2001" -format "%d %b %Y" -locale en_US_roman -gmt 1] \ + [clock scan "12 au 2001" -format "%d %b %Y" -locale en_US_roman -gmt 1] +} {979257600 997574400} +test clock-6.14 {input of uppercase locale token (%b)} { + list [clock scan "12 JA 2001" -format "%d %b %Y" -locale en_US_roman -gmt 1] \ + [clock scan "12 AU 2001" -format "%d %b %Y" -locale en_US_roman -gmt 1] +} {979257600 997574400} +test clock-6.15 {input of ambiguous short locale token (%b)} { + list [catch { + clock scan "12 J 2001" -format "%d %b %Y" -locale en_US_roman -gmt 1 + } result] $result $errorCode +} {1 {input string does not match supplied format} {CLOCK badInputString}} +test clock-6.16 {input of ambiguous short locale token (%b)} { + list [catch { + clock scan "12 Ju 2001" -format "%d %b %Y" -locale en_US_roman -gmt 1 + } result] $result $errorCode +} {1 {input string does not match supplied format} {CLOCK badInputString}} + +test clock-6.17 {spaces are always optional in non-strict mode (default)} { + list [clock scan "2009-06-30T18:30:00+02:00" -format "%Y-%m-%dT%H:%M:%S%z" -gmt 1] \ + [clock scan "2009-06-30T18:30:00 +02:00" -format "%Y-%m-%dT%H:%M:%S%z" -gmt 1] \ + [clock scan "2009-06-30T18:30:00Z" -format "%Y-%m-%dT%H:%M:%S%z" -timezone CET] \ + [clock scan "2009-06-30T18:30:00 Z" -format "%Y-%m-%dT%H:%M:%S%z" -timezone CET] +} {1246379400 1246379400 1246386600 1246386600} + +test clock-6.18 {zone token (%z) is optional} { + list [clock scan "2009-06-30T18:30:00 -01:00" -format "%Y-%m-%dT%H:%M:%S%z" -gmt 1] \ + [clock scan "2009-06-30T18:30:00" -format "%Y-%m-%dT%H:%M:%S%z" -gmt 1] \ + [clock scan " 2009-06-30T18:30:00 " -format "%Y-%m-%dT%H:%M:%S%z" -gmt 1] \ +} {1246390200 1246386600 1246386600} + +test clock-6.19 {no token parsing} { + list [catch { clock scan "%E%O%" -format "%E%O%" }] \ + [catch { clock scan "...%..." -format "...%%..." }] +} {0 0} + +test clock-6.20 {special char tokens %n, %t} { + clock scan "30\t06\t2009\n18\t30" -format "%d%t%m%t%Y%n%H%t%M" -gmt 1 +} 1246386600 + +# Hi, Jeff! +proc _testStarDates {s {days {366*2}} {step {86400}}} { + set step [expr {int($step * 86400)}] + # reconvert - arrange in order of stardate: + set s [set i [clock scan [clock format $s -f "%Q" -g 1] -g 1]] + # test: + set wrong {} + while {$i < $s + $days*86400} { + set d [clock format $i -f "%Q" -g 1] + if {![regexp {^Stardate \d+\.\d$} $d]} { + lappend wrong "wrong: $d -- ($i) -- [clock format $i -g 1]" + } + if {[catch { + set i2 [clock scan $d -f "%Q" -g 1] + } msg]} { + lappend wrong "$d -- ($i) -- [clock format $i -g 1]: $msg" + } + if {$i != $i2} { + lappend wrong "$d -- ($i != $i2) -- [clock format $i -g 1]" + } + incr i $step + } + join $wrong \n +} +test clock-6.21.0 {Stardate 0 day} { + list [set d [clock format -757382400 -format "%Q" -gmt 1]] \ + [clock scan $d -format "%Q" -gmt 1] +} [list "Stardate 00000.0" -757382400] +test clock-6.21.0.1 {Stardate 0.1 - 1.9 (test negative clock value -> positive Stardate)} { + _testStarDates -757382400 2 0.1 +} {} +test clock-6.21.0.2 {Stardate 10000.1 - 10002.9 (test negative clock value -> positive Stardate)} { + _testStarDates [clock scan "Stardate 10000.1" -f %Q -g 1] 3 0.1 +} {} +test clock-6.21.0.2 {Stardate 80000.1 - 80002.9 (test positive clock value)} { + _testStarDates [clock scan "Stardate 80001.1" -f %Q -g 1] 3 0.1 +} {} +test clock-6.21.1 {Stardate} { + list [set d [clock format 1482857280 -format "%Q" -gmt 1]] \ + [clock scan $d -format "%Q" -gmt 1] +} [list "Stardate 70986.7" 1482857280] +test clock-6.21.2 {Stardate next time} { + list [set d [clock format 1482865920 -format "%Q" -gmt 1]] \ + [clock scan $d -format "%Q" -gmt 1] +} [list "Stardate 70986.8" 1482865920] +test clock-6.21.3 {Stardate correct scan over year (leap year, begin, middle and end of the year)} { + _testStarDates [clock scan "01.01.2016" -f "%d.%m.%Y" -g 1] [expr {366*2}] 1 +} {} +rename _testStarDates {} + +test clock-6.22.1 {Greedy match} { + clock format [clock scan "111" -format "%d%m%y" -gmt 1] -locale en -gmt 1 +} {Mon Jan 01 00:00:00 GMT 2001} +test clock-6.22.2 {Greedy match} { + clock format [clock scan "1111" -format "%d%m%y" -gmt 1] -locale en -gmt 1 +} {Thu Jan 11 00:00:00 GMT 2001} +test clock-6.22.3 {Greedy match} { + clock format [clock scan "11111" -format "%d%m%y" -gmt 1] -locale en -gmt 1 +} {Sun Nov 11 00:00:00 GMT 2001} +test clock-6.22.4 {Greedy match} { + clock format [clock scan "111111" -format "%d%m%y" -gmt 1] -locale en -gmt 1 +} {Fri Nov 11 00:00:00 GMT 2011} +test clock-6.22.5 {Greedy match} { + clock format [clock scan "1 1 1" -format "%d%m%y" -gmt 1] -locale en -gmt 1 +} {Mon Jan 01 00:00:00 GMT 2001} +test clock-6.22.6 {Greedy match} { + clock format [clock scan "111 1" -format "%d%m%y" -gmt 1] -locale en -gmt 1 +} {Thu Jan 11 00:00:00 GMT 2001} +test clock-6.22.7 {Greedy match} { + clock format [clock scan "1 111" -format "%d%m%y" -gmt 1] -locale en -gmt 1 +} {Thu Nov 01 00:00:00 GMT 2001} +test clock-6.22.8 {Greedy match} { + clock format [clock scan "1 11 1" -format "%d%m%y" -gmt 1] -locale en -gmt 1 +} {Thu Nov 01 00:00:00 GMT 2001} +test clock-6.22.9 {Greedy match} { + clock format [clock scan "1 11 11" -format "%d%m%y" -gmt 1] -locale en -gmt 1 +} {Tue Nov 01 00:00:00 GMT 2011} +test clock-6.22.10 {Greedy match} { + clock format [clock scan "11 11 11" -format "%d%m%y" -gmt 1] -locale en -gmt 1 +} {Fri Nov 11 00:00:00 GMT 2011} +test clock-6.22.11 {Greedy match} { + clock format [clock scan "1111 120" -format "%y%m%d %H%M%S" -gmt 1] -locale en -gmt 1 +} {Sat Jan 01 01:02:00 GMT 2011} +test clock-6.22.12 {Greedy match} { + clock format [clock scan "11 1 120" -format "%y%m%d %H%M%S" -gmt 1] -locale en -gmt 1 +} {Mon Jan 01 01:02:00 GMT 2001} +test clock-6.22.13 {Greedy match} { + clock format [clock scan "1 11 120" -format "%y%m%d %H%M%S" -gmt 1] -locale en -gmt 1 +} {Mon Jan 01 01:02:00 GMT 2001} +test clock-6.22.14 {Greedy match} { + clock format [clock scan "111120" -format "%y%m%d%H%M%S" -gmt 1] -locale en -gmt 1 +} {Mon Jan 01 01:02:00 GMT 2001} +test clock-6.22.15 {Greedy match} { + clock format [clock scan "1111120" -format "%y%m%d%H%M%S" -gmt 1] -locale en -gmt 1 +} {Sat Jan 01 01:02:00 GMT 2011} +test clock-6.22.16 {Greedy match} { + clock format [clock scan "11121120" -format "%y%m%d%H%M%S" -gmt 1] -locale en -gmt 1 +} {Thu Dec 01 01:02:00 GMT 2011} +test clock-6.22.17 {Greedy match} { + clock format [clock scan "111213120" -format "%y%m%d%H%M%S" -gmt 1] -locale en -gmt 1 +} {Tue Dec 13 01:02:00 GMT 2011} +test clock-6.22.17 {Greedy match (space wins as date-time separator)} { + clock format [clock scan "1112 13120" -format "%y%m%d %H%M%S" -gmt 1] -locale en -gmt 1 +} {Sun Jan 02 13:12:00 GMT 2011} +test clock-6.22.18 {Greedy match (second space wins as date-time separator)} { + clock format [clock scan "1112 13 120" -format "%y%m%d %H%M%S" -gmt 1] -locale en -gmt 1 +} {Tue Dec 13 01:02:00 GMT 2011} +test clock-6.22.19 {Greedy match (space wins as date-time separator)} { + clock format [clock scan "111 213120" -format "%y%m%d %H%M%S" -gmt 1] -locale en -gmt 1 +} {Mon Jan 01 21:31:20 GMT 2001} +test clock-6.22.20 {Greedy match (second space wins as date-time separator)} { + clock format [clock scan "111 2 13120" -format "%y%m%d %H%M%S" -gmt 1] -locale en -gmt 1 +} {Sun Jan 02 13:12:00 GMT 2011} + + test clock-7.1 {Julian Day} { clock scan 0 -format %J -gmt true } -210866803200 @@ -18624,6 +18988,91 @@ test clock-7.10 {Julian Day, negative amount} { [::tcl::mathop::== $s0m1d $s0m24h] [::tcl::mathop::== $J0m24h $J0m1s] } [list -210866889600 -210866889600 -1 -210866803201 -1 -210866803200 0 1 1] +test clock-7.11.1 {Calendar vs Astronomical Julian Day (without and with time fraction)} { + list \ + [clock scan {2440588} -format {%J} -gmt true] \ + [clock scan {2440588} -format {%EJ} -gmt true] \ + [clock scan {2440588} -format {%Ej} -gmt true] \ + [clock scan {2440588.5} -format {%EJ} -gmt true] \ + [clock scan {2440588.5} -format {%Ej} -gmt true] \ +} {0 0 43200 43200 86400} + +test clock-7.11.2 {Astronomical JDN/JD} { + clock scan 0 -format %Ej -gmt true +} -210866760000 + +test clock-7.12 {Astronomical JDN/JD} { + clock format [clock scan 2440587.5 -format %Ej -gmt true] \ + -format "%Y-%m-%d %T" -gmt true +} "1970-01-01 00:00:00" + +test clock-7.13 {Astronomical JDN/JD} { + clock format [clock scan 2451544.5 -format %Ej -gmt true] \ + -format "%Y-%m-%d %T" -gmt true +} "2000-01-01 00:00:00" + +test clock-7.13.1 {Astronomical JDN/JD} { + clock format [clock scan 2488069.5 -format %Ej -gmt true] \ + -format "%Y-%m-%d %T" -gmt true +} "2100-01-01 00:00:00" + +test clock-7.14 {Astronomical JDN/JD} { + clock format [clock scan 5373483.5 -format %Ej -gmt true] \ + -format "%Y-%m-%d %T" -gmt true +} "9999-12-31 00:00:00" + +test clock-7.14.1 {Astronomical JDN/JD} { + clock format [clock scan 5373484 -format %Ej -gmt true] \ + -format "%Y-%m-%d %T" -gmt true +} "9999-12-31 12:00:00" +test clock-7.14.2 {Astronomical JDN/JD} { + clock format [clock scan 5373484.49999 -format %Ej -gmt true] \ + -format "%Y-%m-%d %T" -gmt true +} "9999-12-31 23:59:59" + +test clock-7.15 {Astronomical JDN/JD, bad} { + list [catch { + clock scan bogus -format %Ej + } result] $result $errorCode +} {1 {input string does not match supplied format} {CLOCK badInputString}} + +test clock-7.16 {Astronomical JDN/JD, overflow} { + list [catch { + clock scan 5373484.5 -format %Ej + } result] $result $errorCode \ + [catch { + clock scan 5373485 -format %Ej + } result] $result $errorCode \ + [catch { + clock scan 2147483648 -format %Ej + } result] $result $errorCode \ + [catch { + clock scan 2147483648.5 -format %Ej + } result] $result $errorCode +} [lrepeat 4 1 {requested date too large to represent} {CLOCK dateTooLarge}] + +test clock-7.18 {Astronomical JDN/JD, same precedence as seconds (last wins} { + list [clock scan {2440588 86400} -format {%Ej %s} -gmt true] \ + [clock scan {2440589 0} -format {%Ej %s} -gmt true] \ + [clock scan {86400 2440588} -format {%s %Ej} -gmt true] \ + [clock scan {0 2440589} -format {%s %Ej} -gmt true] +} {86400 0 43200 129600} + +test clock-7.19 {Astronomical JDN/JD, two values} { + clock scan {2440588 2440589} -format {%Ej %Ej} -gmt true +} 129600 + +test clock-7.20 {all JDN/JD are signed (and extended accept floats)} { + set res {} + foreach i {%J %EJ %Ej} { + lappend res [clock scan "-1" -format $i -gmt 1] + } + foreach i {%EJ %Ej} { + lappend res [clock scan "-1.5" -format $i -gmt 1] + } + set res +} {-210866889600 -210866889600 -210866846400 -210866846400 -210866803200} + # BEGIN testcases8 # Test parsing of ccyymmdd @@ -21034,9 +21483,22 @@ test clock-9.1 {seconds take precedence over ccyymmdd} { clock scan {0 20000101} -format {%s %Y%m%d} -gmt true } 0 -test clock-9.2 {Julian day takes precedence over ccyymmdd} { - clock scan {2440588 20000101} -format {%J %Y%m%d} -gmt true -} 0 +test clock-9.2 {Calendar julian day takes precedence over ccyymmdd} { + list \ + [clock scan {2440588 20000101} -format {%J %Y%m%d} -gmt true] \ + [clock scan {2440588 20000101} -format {%EJ %Y%m%d} -gmt true] +} {0 0} +test clock-9.2.1 {Calendar julian day (with time fraction) takes precedence over date-time} { + list \ + [clock scan {2440588.0 20000101 010203} -format {%EJ %Y%m%d %H%M%S} -gmt true] \ + [clock scan {2440588.5 20000101 010203} -format {%EJ %Y%m%d %H%M%S} -gmt true] + +} {0 43200} +test clock-9.3 {Astro julian day takes always precedence over date-time} { + list \ + [clock scan {2440587.5 20000101 010203} -format {%Ej %Y%m%d %H%M%S} -gmt true] \ + [clock scan {2440588 20000101 010203} -format {%Ej %Y%m%d %H%M%S} -gmt true] +} {0 43200} # Test parsing of ccyyddd @@ -21077,80 +21539,87 @@ test clock-10.10 {julian day takes precedence over ccyyddd} { # BEGIN testcases11 -# Test precedence among yyyymmdd and yyyyddd +# Test precedence yyyymmdd over yyyyddd -test clock-11.1 {precedence of ccyyddd and ccyymmdd} { +if {!$valid_mode} { + set res {-result 0} +} else { + set res {-returnCodes error -result "unable to convert input string: ambiguous day"} +} +test clock-11.1 {precedence of ccyymmdd over ccyyddd} -body { clock scan 19700101002 -format %Y%m%d%j -gmt 1 -} 86400 -test clock-11.2 {precedence of ccyyddd and ccyymmdd} { +} {*}$res +test clock-11.2 {precedence of ccyymmdd over ccyyddd} -body { clock scan 01197001002 -format %m%Y%d%j -gmt 1 -} 86400 -test clock-11.3 {precedence of ccyyddd and ccyymmdd} { +} {*}$res +test clock-11.3 {precedence of ccyymmdd over ccyyddd} -body { clock scan 01197001002 -format %d%Y%m%j -gmt 1 -} 86400 -test clock-11.4 {precedence of ccyyddd and ccyymmdd} { +} {*}$res +test clock-11.4 {precedence of ccyymmdd over ccyyddd} -body { clock scan 00219700101 -format %j%Y%m%d -gmt 1 -} 0 -test clock-11.5 {precedence of ccyyddd and ccyymmdd} { +} {*}$res +test clock-11.5 {precedence of ccyymmdd over ccyyddd} -body { clock scan 19700100201 -format %Y%m%j%d -gmt 1 -} 0 -test clock-11.6 {precedence of ccyyddd and ccyymmdd} { +} {*}$res +test clock-11.6 {precedence of ccyymmdd over ccyyddd} -body { clock scan 01197000201 -format %m%Y%j%d -gmt 1 -} 0 -test clock-11.7 {precedence of ccyyddd and ccyymmdd} { +} {*}$res +test clock-11.7 {precedence of ccyymmdd over ccyyddd} -body { clock scan 01197000201 -format %d%Y%j%m -gmt 1 -} 0 -test clock-11.8 {precedence of ccyyddd and ccyymmdd} { +} {*}$res +test clock-11.8 {precedence of ccyymmdd over ccyyddd} -body { clock scan 00219700101 -format %j%Y%d%m -gmt 1 -} 0 -test clock-11.9 {precedence of ccyyddd and ccyymmdd} { +} {*}$res +test clock-11.9 {precedence of ccyymmdd over ccyyddd} -body { clock scan 19700101002 -format %Y%d%m%j -gmt 1 -} 86400 -test clock-11.10 {precedence of ccyyddd and ccyymmdd} { +} {*}$res +test clock-11.10 {precedence of ccyymmdd over ccyyddd} -body { clock scan 01011970002 -format %m%d%Y%j -gmt 1 -} 86400 -test clock-11.11 {precedence of ccyyddd and ccyymmdd} { +} {*}$res +test clock-11.11 {precedence of ccyymmdd over ccyyddd} -body { clock scan 01011970002 -format %d%m%Y%j -gmt 1 -} 86400 -test clock-11.12 {precedence of ccyyddd and ccyymmdd} { +} {*}$res +test clock-11.12 {precedence of ccyymmdd over ccyyddd} -body { clock scan 00201197001 -format %j%m%Y%d -gmt 1 -} 0 -test clock-11.13 {precedence of ccyyddd and ccyymmdd} { +} {*}$res +test clock-11.13 {precedence of ccyymmdd over ccyyddd} -body { clock scan 19700100201 -format %Y%d%j%m -gmt 1 -} 0 -test clock-11.14 {precedence of ccyyddd and ccyymmdd} { +} {*}$res +test clock-11.14 {precedence of ccyymmdd over ccyyddd} -body { clock scan 01010021970 -format %m%d%j%Y -gmt 1 -} 86400 -test clock-11.15 {precedence of ccyyddd and ccyymmdd} { +} {*}$res +test clock-11.15 {precedence of ccyymmdd over ccyyddd} -body { clock scan 01010021970 -format %d%m%j%Y -gmt 1 -} 86400 -test clock-11.16 {precedence of ccyyddd and ccyymmdd} { +} {*}$res +test clock-11.16 {precedence of ccyymmdd over ccyyddd} -body { clock scan 00201011970 -format %j%m%d%Y -gmt 1 -} 0 -test clock-11.17 {precedence of ccyyddd and ccyymmdd} { +} {*}$res +test clock-11.17 {precedence of ccyymmdd over ccyyddd} -body { clock scan 19700020101 -format %Y%j%m%d -gmt 1 -} 0 -test clock-11.18 {precedence of ccyyddd and ccyymmdd} { +} {*}$res +test clock-11.18 {precedence of ccyymmdd over ccyyddd} -body { clock scan 01002197001 -format %m%j%Y%d -gmt 1 -} 0 -test clock-11.19 {precedence of ccyyddd and ccyymmdd} { +} {*}$res +test clock-11.19 {precedence of ccyymmdd over ccyyddd} -body { clock scan 01002197001 -format %d%j%Y%m -gmt 1 -} 0 -test clock-11.20 {precedence of ccyyddd and ccyymmdd} { +} {*}$res +test clock-11.20 {precedence of ccyymmdd over ccyyddd} -body { clock scan 00201197001 -format %j%d%Y%m -gmt 1 -} 0 -test clock-11.21 {precedence of ccyyddd and ccyymmdd} { +} {*}$res +test clock-11.21 {precedence of ccyymmdd over ccyyddd} -body { clock scan 19700020101 -format %Y%j%d%m -gmt 1 -} 0 -test clock-11.22 {precedence of ccyyddd and ccyymmdd} { +} {*}$res +test clock-11.22 {precedence of ccyymmdd over ccyyddd} -body { clock scan 01002011970 -format %m%j%d%Y -gmt 1 -} 0 -test clock-11.23 {precedence of ccyyddd and ccyymmdd} { +} {*}$res +test clock-11.23 {precedence of ccyymmdd over ccyyddd} -body { clock scan 01002011970 -format %d%j%m%Y -gmt 1 -} 0 -test clock-11.24 {precedence of ccyyddd and ccyymmdd} { +} {*}$res +test clock-11.24 {precedence of ccyymmdd over ccyyddd} -body { clock scan 00201011970 -format %j%d%m%Y -gmt 1 -} 0 +} {*}$res + +unset -nocomplain res # END testcases11 # BEGIN testcases12 @@ -21447,11 +21916,11 @@ test clock-12.96 {parse ccyyWwwd} { } 1009756800 # END testcases12 -test clock-13.1 {test that %s takes precedence over ccyyWwwd} { +test clock-13.1 {test that %s takes precedence over ccyyWwwd} valid_off { list [clock scan {0 2000W011} -format {%s %GW%V%u} -gmt true] \ [clock scan {2000W011 0} -format {%GW%V%u %s} -gmt true] } {0 0} -test clock-13.2 {test that %J takes precedence over ccyyWwwd} { +test clock-13.2 {test that %J takes precedence over ccyyWwwd} valid_off { list [clock scan {2440588 2000W011} -format {%J %GW%V%u} -gmt true] \ [clock scan {2000W011 2440588} -format {%GW%V%u %J} -gmt true] } {0 0} @@ -23787,7 +24256,7 @@ test clock-15.2 {yymmdd precedence below julian day} { [clock scan {000101 2440588} -format {%y%m%d %J} -gmt true] } {0 0} -test clock-15.3 {yymmdd precedence below yyyyWwwd} { +test clock-15.3 {yymmdd precedence below yyyyWwwd} valid_off { list [clock scan {1970W014000101} -format {%GW%V%u%y%m%d} -gmt true] \ [clock scan {0001011970W014} -format {%y%m%d%GW%V%u} -gmt true] } {0 0} @@ -23827,7 +24296,7 @@ test clock-16.10 {julian day takes precedence over yyddd} { list [clock scan {2440588 00001} -format {%J %y%j} -gmt true] \ [clock scan {00001 2440588} -format {%Y%j %J} -gmt true] } {0 0} -test clock-16.11 {yyddd precedence below yyyyWwwd} { +test clock-16.11 {yyddd precedence below yyyyWwwd} valid_off { list [clock scan {1970W01400001} -format {%GW%V%u%y%j} -gmt true] \ [clock scan {000011970W014} -format {%y%j%GW%V%u} -gmt true] } {0 0} @@ -24128,7 +24597,7 @@ test clock-17.96 {parse yyWwwd} { # Test precedence of yyWwwd -test clock-18.1 {seconds take precedence over yyWwwd} { +test clock-18.1 {seconds take precedence over yyWwwd} valid_off { list [clock scan {0 00W014} -format {%s %gW%V%u} -gmt true] \ [clock scan {00W014 0} -format {%gW%V%u %s} -gmt true] } {0 0} @@ -24136,11 +24605,11 @@ test clock-18.2 {julian day takes precedence over yyddd} { list [clock scan {2440588 00W014} -format {%J %gW%V%u} -gmt true] \ [clock scan {00W014 2440588} -format {%gW%V%u %J} -gmt true] } {0 0} -test clock-18.3 {yyWwwd precedence below yyyymmdd} { +test clock-18.3 {yyWwwd precedence below yyyymmdd} valid_off { list [clock scan {19700101 00W014} -format {%Y%m%d %gW%V%u} -gmt true] \ [clock scan {00W014 19700101} -format {%gW%V%u %Y%m%d} -gmt true] } {0 0} -test clock-18.4 {yyWwwd precedence below yyyyddd} { +test clock-18.4 {yyWwwd precedence below yyyyddd} valid_off { list [clock scan {1970001 00W014} -format {%Y%j %gW%V%u} -gmt true] \ [clock scan {00W014 1970001} -format {%gW%V%u %Y%j} -gmt true] } {0 0} @@ -25952,42 +26421,48 @@ test clock-26.48 {parse naked day of week} { } 1009670400 # END testcases26 -test clock-27.1 {seconds take precedence over naked weekday} { +if {!$valid_mode} { + set res {-result {0 0}} +} else { + set res {-returnCodes error -result "unable to convert input string: invalid day of week"} +} +test clock-27.1 {seconds take precedence over naked weekday} -body { list [clock scan {0 1} -format {%s %u} -gmt true -base 0] \ [clock scan {1 0} -format {%u %s} -gmt true -base 0] -} {0 0} -test clock-27.2 {julian day takes precedence over naked weekday} { +} {*}$res +test clock-27.2 {julian day takes precedence over naked weekday} -body { list [clock scan {2440588 1} -format {%J %u} -gmt true -base 0] \ [clock scan {1 2440588} -format {%u %J} -gmt true -base 0] -} {0 0} -test clock-27.3 {yyyymmdd over naked weekday} { +} {*}$res +test clock-27.3 {yyyymmdd over naked weekday} -body { list [clock scan {19700101 1} -format {%Y%m%d %u} -gmt true -base 0] \ [clock scan {1 19700101} -format {%u %Y%m%d} -gmt true -base 0] -} {0 0} -test clock-27.4 {yyyyddd over naked weekday} { +} {*}$res +test clock-27.4 {yyyyddd over naked weekday} -body { list [clock scan {1970001 1} -format {%Y%j %u} -gmt true -base 0] \ [clock scan {1 1970001} -format {%u %Y%j} -gmt true -base 0] -} {0 0} -test clock-27.5 {yymmdd over naked weekday} { +} {*}$res +test clock-27.5 {yymmdd over naked weekday} -body { list [clock scan {700101 1} -format {%y%m%d %u} -gmt true -base 0] \ [clock scan {1 700101} -format {%u %y%m%d} -gmt true -base 0] -} {0 0} -test clock-27.6 {yyddd over naked weekday} { +} {*}$res +test clock-27.6 {yyddd over naked weekday} -body { list [clock scan {70001 1} -format {%y%j %u} -gmt true -base 0] \ [clock scan {1 70001} -format {%u %y%j} -gmt true -base 0] -} {0 0} -test clock-27.7 {mmdd over naked weekday} { +} {*}$res +test clock-27.7 {mmdd over naked weekday} -body { list [clock scan {0101 1} -format {%m%d %u} -gmt true -base 0] \ [clock scan {1 0101} -format {%u %m%d} -gmt true -base 0] -} {0 0} -test clock-27.8 {ddd over naked weekday} { +} {*}$res +test clock-27.8 {ddd over naked weekday} -body { list [clock scan {001 1} -format {%j %u} -gmt true -base 0] \ [clock scan {1 001} -format {%u %j} -gmt true -base 0] -} {0 0} -test clock-27.9 {naked day of month over naked weekday} { +} {*}$res +test clock-27.9 {naked day of month over naked weekday} -body { list [clock scan {01 1} -format {%d %u} -gmt true -base 0] \ [clock scan {1 01} -format {%u %d} -gmt true -base 0] -} {0 0} +} {*}$res +unset -nocomplain res test clock-28.1 {base date} { clock scan {} -format {} -gmt true -base 1234567890 @@ -34997,8 +35472,35 @@ test clock-29.1800 {time parsing} { -gmt true -locale en_US_roman \ -format {%J %Ol:%OM:%OS %P} } 86399 + +test clock-29.1811 {parsing of several localized formats} { + set res {} + foreach loc {en de fr} { + foreach fmt {"%x %X" "%X %x"} { + lappend res [clock scan \ + [clock format 0 -format $fmt -locale $loc -gmt 1] \ + -format $fmt -locale $loc -gmt 1] + } + } + set res +} [lrepeat 6 0] +test clock-29.1812 {parsing of several localized formats} { + set res {} + foreach loc {en de fr} { + foreach fmt {"%a %d-%m-%Y" "%a %b %x-%X" "%a, %x %X" "%b, %x %X"} { + lappend res [clock scan \ + [clock format 0 -format $fmt -locale $loc -gmt 1] \ + -format $fmt -locale $loc -gmt 1] + } + } + set res +} [lrepeat 12 0] # END testcases29 + +# BEGIN testcases30 + +# Test [clock add] test clock-30.1 {clock add years} { set t [clock scan 2000-01-01 -format %Y-%m-%d -timezone :UTC] set f [clock add $t 1 year -timezone :UTC] @@ -35243,6 +35745,95 @@ test clock-30.25 {clock add seconds at DST conversion} { set x1 [clock format $f1 -format {%Y-%m-%d %H:%M:%S %z} \ -timezone EST05:00EDT04:00,M4.1.0/02:00,M10.5.0/02:00] } {2004-10-31 01:00:00 -0500} +test clock-30.26 {clock add weekdays} { + set t [clock scan {2013-11-20}] ;# Wednesday + set f1 [clock add $t 3 weekdays] + set x1 [clock format $f1 -format {%Y-%m-%d}] +} {2013-11-25} +test clock-30.27 {clock add weekdays starting on Saturday} { + set t [clock scan {2013-11-23}] ;# Saturday + set f1 [clock add $t 1 weekday] + set x1 [clock format $f1 -format {%Y-%m-%d}] +} {2013-11-25} +test clock-30.28 {clock add weekdays starting on Sunday} { + set t [clock scan {2013-11-24}] ;# Sunday + set f1 [clock add $t 1 weekday] + set x1 [clock format $f1 -format {%Y-%m-%d}] +} {2013-11-25} +test clock-30.29 {clock add 0 weekdays starting on a weekend} { + set t [clock scan {2016-02-27}] ;# Saturday + set f1 [clock add $t 0 weekdays] + set x1 [clock format $f1 -format {%Y-%m-%d}] +} {2016-02-27} +test clock-30.30 {clock add weekdays and back} -body { + set n [clock seconds] + # we start on each day of the week + for {set i 0} {$i < 7} {incr i} { + set start [clock add $n $i days] + set startu [clock format $start -format %u] + # add 0 - 100 weekdays + for {set j 0} {$j < 100} {incr j} { + set forth [clock add $start $j weekdays] + set back [clock add $forth -$j weekdays] + # If $s was a weekday or $j was 0, $b must be the same day. + # Otherwise, $b must be the immediately preceeding Friday + set fail 0 + if {$j == 0 || $startu < 6} { + if {$start != $back} { set fail 1} + } else { + set friday [clock add $start -[expr {$startu % 5}] days] + if {$friday != $back} { set fail 1 } + } + if {$fail} { + set sdate [clock format $start -format {%Y-%m-%d}] + set bdate [clock format $back -format {%Y-%m-%d}] + return "$sdate + $j - $j := $bdate" + } + } + } + return "OK" +} -result {OK} +test clock-30.31 {regression test - add no int overflow} { + list \ + [list \ + [clock add 0 1600000000 seconds 24856 days -gmt 1] \ + [clock add 0 1600000000 seconds 815 months -gmt 1] \ + [clock add 0 1600000000 seconds 69 years -gmt 1] \ + [clock add 0 1600000000 seconds 596524 hours -gmt 1] \ + [clock add 0 1600000000 seconds 35791395 minutes -gmt 1] \ + [clock add 0 1600000000 seconds 0x7fffffff seconds -gmt 1] + ] \ + [list \ + [clock add 1600000000 24856 days -gmt 1] \ + [clock add 1600000000 815 months -gmt 1] \ + [clock add 1600000000 69 years -gmt 1] \ + [clock add 1600000000 596524 hours -gmt 1] \ + [clock add 1600000000 35791395 minutes -gmt 1] \ + [clock add 1600000000 0x7fffffff seconds -gmt 1] + ] +} [lrepeat 2 {3747558400 3743238400 3777452800 3747486400 3747483700 3747483647}] +test clock-30.32 {regression test - add no int overflow} { + list \ + [list \ + [clock add 3777452800 -1600000000 seconds -24856 days -gmt 1] \ + [clock add 3777452800 -1600000000 seconds -815 months -gmt 1] \ + [clock add 3777452800 -1600000000 seconds -69 years -gmt 1] \ + [clock add 3777452800 -1600000000 seconds -596524 hours -gmt 1] \ + [clock add 3777452800 -1600000000 seconds -35791395 minutes -gmt 1] \ + [clock add 3777452800 -1600000000 seconds -0x7fffffff seconds -gmt 1] + ] \ + [list \ + [clock add 2177452800 -24856 days -gmt 1] \ + [clock add 2177452800 -815 months -gmt 1] \ + [clock add 2177452800 -69 years -gmt 1] \ + [clock add 2177452800 -596524 hours -gmt 1] \ + [clock add 2177452800 -35791395 minutes -gmt 1] \ + [clock add 2177452800 -0x7fffffff seconds -gmt 1] + ] +} [lrepeat 2 {29894400 34214400 0 29966400 29969100 29969153}] + +# END testcases30 + test clock-31.1 {system locale} \ -constraints win \ @@ -35612,9 +36203,10 @@ test clock-33.11a {clock test, millis align with micros} { } {1} # clock scan +set syntax "clock scan string ?-base seconds? ?-format string? ?-gmt boolean? ?-locale LOCALE? ?-timezone ZONE? ?-validate boolean?" test clock-34.1 {clock scan tests} { list [catch {clock scan} msg] $msg -} {1 {wrong # args: should be "clock scan string ?-base seconds? ?-format string? ?-gmt boolean? ?-locale LOCALE? ?-timezone ZONE?"}} +} [subst {1 {wrong # args: should be "$syntax"}}] test clock-34.2 {clock scan tests} {*}{ -body {clock scan "bad-string"} -returnCodes error @@ -35648,7 +36240,7 @@ test clock-34.8 {clock scan tests} { } {Oct 23,1992 15:00 GMT} test clock-34.9 {clock scan tests} { list [catch {clock scan "Jan 12" -bad arg} msg] $msg -} {1 {bad option "-bad": must be -base, -format, -gmt, -locale, or -timezone}} +} [subst {1 {bad option "-bad": must be -base, -format, -gmt, -locale, -timezone or -validate}}] # The following two two tests test the two year date policy test clock-34.10 {clock scan tests} { set time [clock scan "1/1/71" -gmt true] @@ -35658,34 +36250,203 @@ test clock-34.11 {clock scan tests} { set time [clock scan "1/1/37" -gmt true] clock format $time -format {%b %d,%Y %H:%M GMT} -gmt true } {Jan 01,2037 00:00 GMT} +test clock-34.11.1 {clock scan tests: same century switch} { + set times [clock scan "1/1/37" -gmt true] +} [clock scan "1/1/37" -format "%m/%d/%y" -gmt true] +test clock-34.11.2 {clock scan tests: same century switch} { + set times [clock scan "1/1/38" -gmt true] +} [clock scan "1/1/38" -format "%m/%d/%y" -gmt true] +test clock-34.11.3 {clock scan tests: same century switch} { + set times [clock scan "1/1/39" -gmt true] +} [clock scan "1/1/39" -format "%m/%d/%y" -gmt true] test clock-34.12 {clock scan, relative times} { - set time [clock scan "Oct 23, 1992 -1 day"] - clock format $time -format {%b %d, %Y} + set time [clock scan "Oct 23, 1992 -1 day" -gmt true] + clock format $time -format {%b %d, %Y} -gmt true } "Oct 22, 1992" + test clock-34.13 {clock scan, ISO 8601 base date format} { - set time [clock scan "19921023"] - clock format $time -format {%b %d, %Y} + set time [clock scan "19921023" -gmt true] + clock format $time -format {%b %d, %Y} -gmt true } "Oct 23, 1992" test clock-34.14 {clock scan, ISO 8601 expanded date format} { - set time [clock scan "1992-10-23"] - clock format $time -format {%b %d, %Y} + set time [clock scan "1992-10-23" -gmt true] + clock format $time -format {%b %d, %Y} -gmt true } "Oct 23, 1992" test clock-34.15 {clock scan, DD-Mon-YYYY format} { - set time [clock scan "23-Oct-1992"] - clock format $time -format {%b %d, %Y} + set time [clock scan "23-Oct-1992" -gmt true] + clock format $time -format {%b %d, %Y} -gmt true } "Oct 23, 1992" test clock-34.16 {clock scan, ISO 8601 point in time format} { - set time [clock scan "19921023T235959"] - clock format $time -format {%b %d, %Y %H:%M:%S} + set time [clock scan "19921023T235959" -gmt true] + clock format $time -format {%b %d, %Y %H:%M:%S} -gmt true +} "Oct 23, 1992 23:59:59" +test clock-34.16.1a {clock scan, ISO 8601 T literal optional (YYYYMMDDhhmmss)} { + set time [clock scan "19921023235959" -gmt true] + clock format $time -format {%b %d, %Y %H:%M:%S} -gmt true +} "Oct 23, 1992 23:59:59" +test clock-34.16.1b {clock scan, ISO 8601 T literal optional (YYYYMMDDhhmm)} { + set time [clock scan "199210232359" -gmt true] + clock format $time -format {%b %d, %Y %H:%M:%S} -gmt true +} "Oct 23, 1992 23:59:00" +test clock-34.16.2 {clock scan, ISO 8601 extended date time} { + set time [clock scan "1992-10-23T23:59:59" -gmt true] + clock format $time -format {%b %d, %Y %H:%M:%S} -gmt true } "Oct 23, 1992 23:59:59" test clock-34.17 {clock scan, ISO 8601 point in time format} { - set time [clock scan "19921023 235959"] - clock format $time -format {%b %d, %Y %H:%M:%S} + set time [clock scan "19921023 235959" -gmt true] + clock format $time -format {%b %d, %Y %H:%M:%S} -gmt true +} "Oct 23, 1992 23:59:59" +test clock-34.17.2a {clock scan, ISO 8601 extended date time (YYYY-MM-DD hh:mm:ss)} { + set time [clock scan "1992-10-23 23:59:59" -gmt true] + clock format $time -format {%b %d, %Y %H:%M:%S} -gmt true +} "Oct 23, 1992 23:59:59" +test clock-34.17.2b {clock scan, ISO 8601 extended date time (YYYY-MM-DDThh:mm:ss)} { + set time [clock scan "1992-10-23T23:59:59" -gmt true] + clock format $time -format {%b %d, %Y %H:%M:%S} -gmt true +} "Oct 23, 1992 23:59:59" +test clock-34.17.2c {clock scan, ISO 8601 extended date time (YYYY-MM-DD hh:mm)} { + set time [clock scan "1992-10-23 23:59" -gmt true] + clock format $time -format {%b %d, %Y %H:%M:%S} -gmt true +} "Oct 23, 1992 23:59:00" +test clock-34.17.2d {clock scan, ISO 8601 extended date time (YYYY-MM-DDThh:mm)} { + set time [clock scan "1992-10-23T23:59" -gmt true] + clock format $time -format {%b %d, %Y %H:%M:%S} -gmt true +} "Oct 23, 1992 23:59:00" +test clock-34.17.3 {clock scan, TZ-word boundaries - Z is not TZ here } -body { + set time [clock scan "1992-10-23Z23:59:59" -gmt true] + clock format $time -format {%b %d, %Y %H:%M:%S} -gmt true +} -returnCodes error -match glob \ + -result {unable to convert date-time string*} +test clock-34.17.4 {clock scan, TZ-word boundaries - Z is TZ UTC here} { + set time [clock scan "1992-10-23 Z 23:59:59" -gmt true] + clock format $time -format {%b %d, %Y %H:%M:%S} -gmt true +} "Oct 23, 1992 23:59:59" +test clock-34.17.5 {clock scan, ISO 8601 extended date time with UTC TZ} { + set time [clock scan "1992-10-23T23:59:59Z" -timezone :America/Detroit] + clock format $time -format {%b %d, %Y %H:%M:%S} -gmt true } "Oct 23, 1992 23:59:59" test clock-34.18 {clock scan, ISO 8601 point in time format} { - set time [clock scan "19921023T000000"] - clock format $time -format {%b %d, %Y %H:%M:%S} + set time [clock scan "19921023T000000" -gmt true] + clock format $time -format {%b %d, %Y %H:%M:%S} -gmt true +} "Oct 23, 1992 00:00:00" +test clock-34.18.2 {clock scan, ISO 8601 extended date time} { + set time [clock scan "1992-10-23T00:00:00" -gmt true] + clock format $time -format {%b %d, %Y %H:%M:%S} -gmt true +} "Oct 23, 1992 00:00:00" +test clock-34.18.3 {clock scan, TZ-word boundaries - Z is not TZ here } -body { + set time [clock scan "1992-10-23Z00:00:00" -gmt true] + clock format $time -format {%b %d, %Y %H:%M:%S} -gmt true +} -returnCodes error -match glob \ + -result {unable to convert date-time string*} +test clock-34.18.4 {clock scan, TZ-word boundaries - Z is TZ UTC here} { + set time [clock scan "1992-10-23 Z 00:00:00" -gmt true] + clock format $time -format {%b %d, %Y %H:%M:%S} -gmt true } "Oct 23, 1992 00:00:00" +test clock-34.18.5 {clock scan, ISO 8601 extended date time with UTC TZ} { + set time [clock scan "1992-10-23T00:00:00Z" -timezone :America/Detroit] + clock format $time -format {%b %d, %Y %H:%M:%S} -gmt true +} "Oct 23, 1992 00:00:00" + +test clock-34.20.1 {clock scan tests (-TZ)} { + set time [clock scan "31 Jan 14 23:59:59 -0100" -gmt true] + clock format $time -format {%b %d,%Y %H:%M:%S %Z} -gmt true +} {Feb 01,2014 00:59:59 GMT} +test clock-34.20.2 {clock scan tests (+TZ)} { + set time [clock scan "31 Jan 14 23:59:59 +0100" -gmt true] + clock format $time -format {%b %d,%Y %H:%M:%S %Z} -gmt true +} {Jan 31,2014 22:59:59 GMT} +test clock-34.20.3 {clock scan tests (-TZ)} { + set time [clock scan "23:59:59 -0100" -base 0 -gmt true] + clock format $time -format {%b %d,%Y %H:%M:%S %Z} -gmt true +} {Jan 02,1970 00:59:59 GMT} +test clock-34.20.4 {clock scan tests (+TZ)} { + set time [clock scan "23:59:59 +0100" -base 0 -gmt true] + clock format $time -format {%b %d,%Y %H:%M:%S %Z} -gmt true +} {Jan 01,1970 22:59:59 GMT} +test clock-34.20.5 {clock scan tests (TZ)} { + set time [clock scan "Mon, 30 Jun 2014 23:59:59 CEST" -gmt true] + clock format $time -format {%b %d,%Y %H:%M:%S %Z} -gmt true +} {Jun 30,2014 21:59:59 GMT} +test clock-34.20.6 {clock scan tests (TZ)} { + set time [clock scan "Fri, 31 Jan 2014 23:59:59 CET" -gmt true] + clock format $time -format {%b %d,%Y %H:%M:%S %Z} -gmt true +} {Jan 31,2014 22:59:59 GMT} +test clock-34.20.7 {clock scan tests (relspec, day unit not TZ)} { + set time [clock scan "23:59:59 +15 day" -base 2000000 -gmt true] + clock format $time -format {%b %d,%Y %H:%M:%S %Z} -gmt true +} {Feb 08,1970 23:59:59 GMT} +test clock-34.20.8 {clock scan tests (relspec, day unit not TZ)} { + set time [clock scan "23:59:59 -15 day" -base 2000000 -gmt true] + clock format $time -format {%b %d,%Y %H:%M:%S %Z} -gmt true +} {Jan 09,1970 23:59:59 GMT} +test clock-34.20.9 {clock scan tests (merid and TZ)} { + set time [clock scan "10:59 pm CET" -base 2000000 -gmt true] + clock format $time -format {%b %d,%Y %H:%M:%S %Z} -gmt true +} {Jan 24,1970 21:59:00 GMT} +test clock-34.20.10 {clock scan tests (merid and TZ)} { + set time [clock scan "10:59 pm +0100" -base 2000000 -gmt true] + clock format $time -format {%b %d,%Y %H:%M:%S %Z} -gmt true +} {Jan 24,1970 21:59:00 GMT} +test clock-34.20.11 {clock scan tests (complex TZ)} { + list [clock scan "GMT+1000" -base 100000000 -gmt 1] \ + [clock scan "GMT+10" -base 100000000 -gmt 1] \ + [clock scan "+1000" -base 100000000 -gmt 1] +} [lrepeat 3 99964000] +test clock-34.20.12 {clock scan tests (complex TZ)} { + list [clock scan "GMT-1000" -base 100000000 -gmt 1] \ + [clock scan "GMT-10" -base 100000000 -gmt 1] \ + [clock scan "-1000" -base 100000000 -gmt 1] +} [lrepeat 3 100036000] +test clock-34.20.13 {clock scan tests (complex TZ)} { + list [clock scan "GMT-0000" -base 100000000 -gmt 1] \ + [clock scan "GMT+0000" -base 100000000 -gmt 1] \ + [clock scan "GMT" -base 100000000 -gmt 1] +} [lrepeat 3 100000000] +test clock-34.20.14 {clock scan tests (complex TZ)} { + list [clock scan "CET+1000" -base 100000000 -gmt 1] \ + [clock scan "CET-1000" -base 100000000 -gmt 1] +} {99960400 100032400} +test clock-34.20.15 {clock scan tests (complex TZ)} { + list [clock scan "CET-0000" -base 100000000 -gmt 1] \ + [clock scan "CET+0000" -base 100000000 -gmt 1] \ + [clock scan "CET" -base 100000000 -gmt 1] +} [lrepeat 3 99996400] +test clock-34.20.16 {clock scan tests (complex TZ)} { + list [clock format [clock scan "00:00 GMT+1000" -base 100000000 -gmt 1] -gmt 1] \ + [clock format [clock scan "00:00 GMT+10" -base 100000000 -gmt 1] -gmt 1] \ + [clock format [clock scan "00:00 +1000" -base 100000000 -gmt 1] -gmt 1] \ + [clock format [clock scan "00:00" -base 100000000 -timezone +1000] -gmt 1] +} [lrepeat 4 "Fri Mar 02 14:00:00 GMT 1973"] +test clock-34.20.17 {clock scan tests (complex TZ)} { + list [clock format [clock scan "00:00 GMT+0100" -base 100000000 -gmt 1] -gmt 1] \ + [clock format [clock scan "00:00 GMT+01" -base 100000000 -gmt 1] -gmt 1] \ + [clock format [clock scan "00:00 GMT+1" -base 100000000 -gmt 1] -gmt 1] \ + [clock format [clock scan "00:00" -base 100000000 -timezone +0100] -gmt 1] +} [lrepeat 4 "Fri Mar 02 23:00:00 GMT 1973"] +test clock-34.20.18 {clock scan tests (no TZ)} { + list [clock scan "1000days" -base 100000000 -gmt 1] \ + [clock scan "1000 days" -base 100000000 -gmt 1] \ + [clock scan "+1000days" -base 100000000 -gmt 1] \ + [clock scan "+1000 days" -base 100000000 -gmt 1] \ + [clock scan "GMT +1000 days" -base 100000000 -gmt 1] \ + [clock scan "00:00 GMT +1000 days" -base 100000000 -gmt 1] +} [lrepeat 6 186364800] +test clock-34.20.19 {clock scan tests (no TZ)} { + list [clock scan "-1000days" -base 100000000 -gmt 1] \ + [clock scan "-1000 days" -base 100000000 -gmt 1] \ + [clock scan "GMT -1000days" -base 100000000 -gmt 1] \ + [clock scan "00:00 GMT -1000 days" -base 100000000 -gmt 1] \ +} [lrepeat 4 13564800] +test clock-34.20.20 {clock scan tests (TZ, TZ + 1day)} { + clock scan "00:00 GMT+1000 day" -base 100000000 -gmt 1 +} 100015200 +test clock-34.20.21 {clock scan tests (local date of base depends on given TZ, time apllied to different day)} { + list [clock scan "23:59:59 -0100" -base 0 -timezone :CET] \ + [clock scan "23:59:59 -0100" -base 0 -gmt 1] \ + [clock scan "23:59:59 -0100" -base 0 -timezone -1400] \ + [clock scan "23:59:59 -0100" -base 0 -timezone :Pacific/Apia] +} {89999 89999 3599 3599} + # CLOCK SCAN REAL TESTS # We use 5am PST, 31-12-1999 as the base for these scans because irrespective @@ -35780,6 +36541,27 @@ test clock-34.40 {clock scan, next day of week} { clock format [clock scan "next thursday" -base [clock scan 20000112]] \ -format {%b %d, %Y} } "Jan 20, 2000" +test clock-34.40.1 {clock scan, ordinal month after relative date} { + # This will fail without the bug fix (clock.tcl), as still missing + # month/julian day conversion before ordinal month increment + clock format [ \ + clock scan "5 years 18 months 387 days" -base 0 -gmt 1 + ] -format {%a, %b %d, %Y} -gmt 1 -locale en_US_roman +} "Sat, Jul 23, 1977" +test clock-34.40.2 {clock scan, ordinal month after relative date} { + # This will fail without the bug fix (clock.tcl), as still missing + # month/julian day conversion before ordinal month increment + clock format [ \ + clock scan "5 years 18 months 387 days next Jan" -base 0 -gmt 1 + ] -format {%a, %b %d, %Y} -gmt 1 -locale en_US_roman +} "Mon, Jan 23, 1978" +test clock-34.40.3 {clock scan, day of week after ordinal date} { + # This will fail without the bug fix (clock.tcl), because the relative + # week day should be applied after whole date conversion + clock format [ \ + clock scan "5 years 18 months 387 days next January Fri" -base 0 -gmt 1 + ] -format {%a, %b %d, %Y} -gmt 1 -locale en_US_roman +} "Fri, Jan 27, 1978" # weekday specification and base. test clock-34.41 {2nd monday in november} { @@ -35931,6 +36713,94 @@ test clock-34.68 {clock scan tests (merid and TZ)} { clock format $time -format {%b %d,%Y %H:%M:%S %Z} -gmt true } {Jan 24,1970 21:59:00 GMT} +test clock-34.69.1 {relative from base, date switch} { + set base [clock scan "12/31/2016 23:59:59" -gmt 1] + clock format [clock scan "+1 second" \ + -base $base -gmt 1] -gmt 1 -format {%Y-%m-%d %H:%M:%S} +} {2017-01-01 00:00:00} +test clock-34.69.2 {relative time, daylight switch} { + set base [clock scan "03/27/2016" -timezone CET] + set res {} + lappend res [clock format [clock scan "+1 hour" \ + -base $base -timezone CET] -timezone CET -format {%Y-%m-%d %H:%M:%S %Z}] + lappend res [clock format [clock scan "+2 hour" \ + -base $base -timezone CET] -timezone CET -format {%Y-%m-%d %H:%M:%S %Z}] +} {{2016-03-27 01:00:00 CET} {2016-03-27 03:00:00 CEST}} + +test clock-34.69.3 {relative time with day increment / daylight switch} { + set base [clock scan "03/27/2016" -timezone CET] + set res {} + lappend res [clock format [clock scan "+5 day +25 hour" \ + -base [expr {$base - 6*24*60*60}] -timezone CET] -timezone CET -format {%Y-%m-%d %H:%M:%S %Z}] + lappend res [clock format [clock scan "+5 day +26 hour" \ + -base [expr {$base - 6*24*60*60}] -timezone CET] -timezone CET -format {%Y-%m-%d %H:%M:%S %Z}] +} {{2016-03-27 01:00:00 CET} {2016-03-27 03:00:00 CEST}} + +test clock-34.69.4 {relative time with month & day increment / daylight switch} { + set base [clock scan "03/27/2016" -timezone CET] + set res {} + lappend res [clock format [clock scan "next Mar +5 day +25 hour" \ + -base [expr {$base - 35*24*60*60}] -timezone CET] -timezone CET -format {%Y-%m-%d %H:%M:%S %Z}] + lappend res [clock format [clock scan "next Mar +5 day +26 hour" \ + -base [expr {$base - 35*24*60*60}] -timezone CET] -timezone CET -format {%Y-%m-%d %H:%M:%S %Z}] +} {{2016-03-27 01:00:00 CET} {2016-03-27 03:00:00 CEST}} + +test clock-34.70.1 {check date in DST-hole: daylight switch CET -> CEST} { + set res {} + # forwards + set base 1459033200 + for {set i 0} {$i <= 3} {incr i} { + set d [clock scan "+$i hour" -base $base -timezone CET] + lappend res "$d = [clock format $d -timezone CET -format {%Y-%m-%d %H:%M:%S %Z}]" + } + lappend res "#--" + # backwards + set base 1459044000 + for {set i 0} {$i <= 3} {incr i} { + set d [clock scan "-$i hour" -base $base -timezone CET] + lappend res "$d = [clock format $d -timezone CET -format {%Y-%m-%d %H:%M:%S %Z}]" + } + set res +} [split [regsub -all {^\n|\n$} { +1459033200 = 2016-03-27 00:00:00 CET +1459036800 = 2016-03-27 01:00:00 CET +1459040400 = 2016-03-27 03:00:00 CEST +1459044000 = 2016-03-27 04:00:00 CEST +#-- +1459044000 = 2016-03-27 04:00:00 CEST +1459040400 = 2016-03-27 03:00:00 CEST +1459036800 = 2016-03-27 01:00:00 CET +1459033200 = 2016-03-27 00:00:00 CET +} {}] \n] + +test clock-34.70.2 {check date in DST-hole: daylight switch CEST -> CET} { + set res {} + # forwards + set base 1477782000 + for {set i 0} {$i <= 3} {incr i} { + set d [clock scan "+$i hour" -base $base -timezone CET] + lappend res "$d = [clock format $d -timezone CET -format {%Y-%m-%d %H:%M:%S %Z}]" + } + lappend res "#--" + # backwards + set base 1477792800 + for {set i 0} {$i <= 3} {incr i} { + set d [clock scan "-$i hour" -base $base -timezone CET] + lappend res "$d = [clock format $d -timezone CET -format {%Y-%m-%d %H:%M:%S %Z}]" + } + set res +} [split [regsub -all {^\n|\n$} { +1477782000 = 2016-10-30 01:00:00 CEST +1477785600 = 2016-10-30 02:00:00 CEST +1477789200 = 2016-10-30 02:00:00 CET +1477792800 = 2016-10-30 03:00:00 CET +#-- +1477792800 = 2016-10-30 03:00:00 CET +1477789200 = 2016-10-30 02:00:00 CET +1477785600 = 2016-10-30 02:00:00 CEST +1477782000 = 2016-10-30 01:00:00 CEST +} {}] \n] + # clock seconds test clock-35.1 {clock seconds tests} { expr {[clock seconds] + 1} @@ -35961,13 +36831,33 @@ test clock-36.3 {clock scan next monthname} { } "05.2001" test clock-37.1 {%s gmt testing} { - set s [clock seconds] + set s [clock scan "2017-05-10 09:00:00" -gmt 1] set a [clock format $s -format %s -gmt 0] set b [clock format $s -format %s -gmt 1] + set c [clock scan $s -format %s -gmt 0] + set d [clock scan $s -format %s -gmt 1] # %s, being the difference between local and Greenwich, does not # depend on the time zone. - set c [expr {$b-$a}] -} {0} + list [expr {$b-$a}] [expr {$d-$c}] +} {0 0} +test clock-37.2 {%Es gmt testing CET} { + set s [clock scan "2017-01-10 09:00:00" -gmt 1] + set a [clock format $s -format %Es -timezone CET] + set b [clock format $s -format %Es -gmt 1] + set c [clock scan $s -format %Es -timezone CET] + set d [clock scan $s -format %Es -gmt 1] + # %Es depend on the time zone (local seconds instead of posix seconds). + list [expr {$b-$a}] [expr {$d-$c}] +} {-3600 3600} +test clock-37.3 {%Es gmt testing CEST} { + set s [clock scan "2017-05-10 09:00:00" -gmt 1] + set a [clock format $s -format %Es -timezone CET] + set b [clock format $s -format %Es -gmt 1] + set c [clock scan $s -format %Es -timezone CET] + set d [clock scan $s -format %Es -gmt 1] + # %Es depend on the time zone (local seconds instead of posix seconds). + list [expr {$b-$a}] [expr {$d-$c}] +} {-7200 7200} test clock-38.1 {regression - convertUTCToLocalViaC - east of Greenwich} \ -setup { @@ -36021,6 +36911,52 @@ test clock-38.2 {make sure TZ is not cached after unset} \ } \ -result 1 +test clock-38.3sc {ensure cache of base is correct for :localtime if TZ-env changing / scan} \ + -setup { + if { [info exists env(TZ)] } { + set oldTZ $env(TZ) + } + } \ + -body { + set res {} + foreach env(TZ) {GMT-11:30 GMT-07:30 GMT-03:30 GMT} \ + i {{07:30:00} {03:30:00} {23:30:00} {20:00:00}} \ + { + lappend res [clock scan $i -format "%H:%M:%S" -base [expr {20*60*60}] -timezone :localtime] + } + set res + } \ + -cleanup { + if { [info exists oldTZ] } { + set env(TZ) $oldTZ + unset oldTZ + } else { + unset env(TZ) + } + } \ + -result [lrepeat 4 [expr {20*60*60}]] +test clock-38.3fm {ensure cache of base is correct for :localtime if TZ-env changing / format} \ + -setup { + if { [info exists env(TZ)] } { + set oldTZ $env(TZ) + } + } \ + -body { + set res {} + foreach env(TZ) {GMT-11:30 GMT-07:30 GMT-03:30 GMT} { + lappend res [clock format [expr {20*60*60}] -format "%Y-%m-%dT%H:%M:%S %Z" -timezone :localtime] + } + set res + } \ + -cleanup { + if { [info exists oldTZ] } { + set env(TZ) $oldTZ + unset oldTZ + } else { + unset env(TZ) + } + } \ + -result {{1970-01-02T07:30:00 +1130} {1970-01-02T03:30:00 +0730} {1970-01-01T23:30:00 +0330} {1970-01-01T20:00:00 +0000}} test clock-39.1 {regression - synonym timezones} { clock format 0 -format {%H:%M:%S} -timezone :US/Eastern @@ -36092,30 +37028,320 @@ test clock-44.1 {regression test - time zone name containing hyphen } \ } } \ -result {12:34:56-0500} - -test clock-45.1 {regression test - time zone containing only two digits} \ +test clock-44.2 {regression test - time zone containing only two digits} \ -body { clock scan 1985-04-12T10:15:30+04 -format %Y-%m-%dT%H:%M:%S%Z } \ -result 482134530 -test clock-46.1 {regression test - month zero} \ +test clock-45.1 {compat: scan regression on spaces (multiple spaces in format)} \ + -body { + list \ + [clock scan "11/08/2018 0612" -format "%m/%d/%Y %H%M" -gmt 1] \ + [clock scan "11/08/2018 0612" -format "%m/%d/%Y %H%M" -gmt 1] \ + [clock scan "11/08/2018 0612" -format "%m/%d/%Y %H%M" -gmt 1] \ + [clock scan " 11/08/2018 0612" -format " %m/%d/%Y %H%M" -gmt 1] \ + [clock scan " 11/08/2018 0612" -format " %m/%d/%Y %H%M" -gmt 1] \ + [clock scan " 11/08/2018 0612" -format " %m/%d/%Y %H%M" -gmt 1] \ + [clock scan "11/08/2018 0612 " -format "%m/%d/%Y %H%M " -gmt 1] \ + [clock scan "11/08/2018 0612 " -format "%m/%d/%Y %H%M " -gmt 1] \ + [clock scan "11/08/2018 0612 " -format "%m/%d/%Y %H%M " -gmt 1] + } -result [lrepeat 9 1541657520] + +test clock-45.2 {compat: scan regression on spaces (multiple leading/trailing spaces in input)} \ + -body { + set sp [string repeat " " 20] + list \ + [clock scan "NOV 7${sp}" -format "%b %d" -base 0 -gmt 1 -locale en] \ + [clock scan "${sp}NOV 7" -format "%b %d" -base 0 -gmt 1 -locale en] \ + [clock scan "${sp}NOV 7${sp}" -format "%b %d" -base 0 -gmt 1 -locale en] \ + [clock scan "1970 NOV 7${sp}" -format "%Y %b %d" -gmt 1 -locale en] \ + [clock scan "${sp}1970 NOV 7" -format "%Y %b %d" -gmt 1 -locale en] \ + [clock scan "${sp}1970 NOV 7${sp}" -format "%Y %b %d" -gmt 1 -locale en] + } -result [lrepeat 6 26784000] +test clock-45.3 {compat: scan regression on spaces (shortest match)} \ + -body { + list \ + [clock scan "11 1 120" -format "%y%m%d %H%M%S" -gmt 1] \ + [clock scan "11 1 120 " -format "%y%m%d %H%M%S" -gmt 1] \ + [clock scan " 11 1 120" -format "%y%m%d %H%M%S" -gmt 1] \ + [clock scan "11 1 120 " -format "%y%m%d %H%M%S " -gmt 1] \ + [clock scan " 11 1 120" -format " %y%m%d %H%M%S" -gmt 1] + } -result [lrepeat 5 978310920] +test clock-45.4 {compat: scan regression on spaces (mandatory leading/trailing spaces in format)} \ + -body { + list \ + [catch {clock scan "11 1 120" -format "%y%m%d %H%M%S " -gmt 1} ret] $ret \ + [catch {clock scan "11 1 120" -format " %y%m%d %H%M%S" -gmt 1} ret] $ret \ + [catch {clock scan "11 1 120" -format " %y%m%d %H%M%S " -gmt 1} ret] $ret + } -result [lrepeat 3 1 "input string does not match supplied format"] +test clock-45.5 {regression test - freescan no int overflow} { + # note that the relative date changes currently reset the time to 00:00, + # this can be changed later (simply achievable by adding 00:00 if expected): + list \ + [clock scan "+24856 days" -base 1600000000 -gmt 1] \ + [clock scan "+815 months" -base 1600000000 -gmt 1] \ + [clock scan "+69 years" -base 1600000000 -gmt 1] \ + [clock scan "+596524 hours" -base 1600000000 -gmt 1] \ + [clock scan "+35791395 minutes" -base 1600000000 -gmt 1] \ + [clock scan "+2147483647 seconds" -base 1600000000 -gmt 1] +} {3747513600 3743193600 3777408000 3747486400 3747483700 3747483647} +test clock-45.6 {regression test - freescan no int overflow} { + # note that the relative date changes currently reset the time to 00:00, + # this can be changed later (simply achievable by adding 00:00 if expected): + list \ + [clock scan "-24856 days" -base 2177452800 -gmt 1] \ + [clock scan "-815 months" -base 2177452800 -gmt 1] \ + [clock scan "-69 years" -base 2177452800 -gmt 1] \ + [clock scan "-596524 hours" -base 2177452800 -gmt 1] \ + [clock scan "-35791395 minutes" -base 2177452800 -gmt 1] \ + [clock scan "-2147483647 seconds" -base 2177452800 -gmt 1] +} {29894400 34214400 0 29966400 29969100 29969153} + +test clock-46.1 {regression test - month zero} -constraints valid_off \ -body { clock scan 2004-00-00 -format %Y-%m-%d } -result [clock scan 2003-11-30 -format %Y-%m-%d] -test clock-46.2 {regression test - month zero} \ +test clock-46.2 {regression test - month zero} -constraints valid_off \ -body { clock scan 20040000 } -result [clock scan 2003-11-30 -format %Y-%m-%d] -test clock-46.3 {regression test - month thirteen} \ +test clock-46.3 {regression test - month thirteen} -constraints valid_off \ -body { clock scan 2004-13-01 -format %Y-%m-%d } -result [clock scan 2005-01-01 -format %Y-%m-%d] -test clock-46.4 {regression test - month thirteen} \ +test clock-46.4 {regression test - month thirteen} -constraints valid_off \ -body { clock scan 20041301 } -result [clock scan 2005-01-01 -format %Y-%m-%d] +test clock-46.5 {regression test - good time} \ + -body { + # 12:01 apm are valid input strings... + list [clock scan "12:01 am" -base 0 -gmt 1] \ + [clock scan "12:01 pm" -base 0 -gmt 1] + } -result {60 43260} +test clock-46.6 {freescan: regression test - bad time} -constraints valid_off \ + -body { + # 13:00 am/pm are invalid input strings... + list [clock scan "13:00 am" -base 0 -gmt 1] \ + [clock scan "13:00 pm" -base 0 -gmt 1] + } -result {-1 -1} + +proc _invalid_test {args} { + global valid_mode + # ensure validation works TZ independently, since the conversion + # of local time to UTC may adjust date/time tokens, depending on TZ: + set res {} + foreach tz {:GMT :CET {} :Europe/Berlin :localtime} { + foreach {v} $args { + if {$valid_mode} { # globally -valid 1 + lappend res [catch {clock scan $v -timezone $tz} msg] $msg + } else { + lappend res [catch {clock scan $v -valid 1 -timezone $tz} msg] $msg + } + } + } + set res +} +# test without and with relative offsets: +foreach {idx relstr} {"" "" "+rel" "+ 15 month + 40 days + 30 hours + 80 minutes +9999 seconds"} { +test clock-46.10$idx {freescan: validation rules: invalid time} \ + -body { + # 13:00 am/pm are invalid input strings... + _invalid_test "13:00 am$relstr" "13:00 pm$relstr" + } -result [lrepeat 10 1 {unable to convert input string: invalid time (hour)}] +test clock-46.11$idx {freescan: validation rules: invalid time} \ + -body { + # invalid minutes in input strings... + _invalid_test "23:70$relstr" "11:80 pm$relstr" + } -result [lrepeat 10 1 {unable to convert input string: invalid time (minutes)}] +test clock-46.12$idx {freescan: validation rules: invalid time} \ + -body { + # invalid seconds in input strings... + _invalid_test "23:00:70$relstr" "11:00:80 pm$relstr" + } -result [lrepeat 10 1 {unable to convert input string: invalid time}] +test clock-46.13$idx {freescan: validation rules: invalid day} \ + -body { + _invalid_test "29 Feb 2017$relstr" "30 Feb 2016$relstr" + } -result [lrepeat 10 1 {unable to convert input string: invalid day}] +test clock-46.14$idx {freescan: validation rules: invalid day} \ + -body { + _invalid_test "0 Feb 2017$relstr" "00 Feb 2017$relstr" + } -result [lrepeat 10 1 {unable to convert input string: invalid day}] +test clock-46.15$idx {freescan: validation rules: invalid month} \ + -body { + _invalid_test "13/13/2017$relstr" "00/00/2017$relstr" + } -result [lrepeat 10 1 {unable to convert input string: invalid month}] +test clock-46.16$idx {freescan: validation rules: invalid day of week} \ + -body { + _invalid_test "Sat Jan 02 00:00:00 1970$relstr" "Thu Jan 04 00:00:00 1970$relstr" + } -result [lrepeat 10 1 {unable to convert input string: invalid day of week}] +test clock-46.17$idx {scan: validation rules: invalid year} -setup { + set orgcfg [list -min-year [::tcl::unsupported::clock::configure -min-year] -max-year [::tcl::unsupported::clock::configure -max-year] \ + -year-century [::tcl::unsupported::clock::configure -year-century] -century-switch [::tcl::unsupported::clock::configure -century-switch]] + ::tcl::unsupported::clock::configure -min-year 2000 -max-year 2100 -year-century 2000 -century-switch 38 + } -body { + _invalid_test "70-01-01$relstr" "1870-01-01$relstr" "9570-01-01$relstr" + } -result [lrepeat 15 1 {unable to convert input string: invalid year}] -cleanup { + ::tcl::unsupported::clock::configure {*}$orgcfg + unset -nocomplain orgcfg + } + +}; # foreach +rename _invalid_test {} +unset -nocomplain idx relstr + +set dst_hole_check { + {":Europe/Berlin" + "2017-03-26 01:59:59" "2017-03-26 02:00:00" "2017-03-26 02:59:59" "2017-03-26 03:00:00" + "2017-10-29 01:59:59" "2017-10-29 02:00:00"} + {":Europe/Berlin" + "2018-03-25 01:59:59" "2018-03-25 02:00:00" "2018-03-25 02:59:59" "2018-03-25 03:00:00" + "2018-10-28 01:59:59" "2018-10-28 02:00:00"} + {":America/New_York" + "2017-03-12 01:59:59" "2017-03-12 02:00:00" "2017-03-12 02:59:59" "2017-03-12 03:00:00" + "2017-11-05 01:59:59" "2017-11-05 02:00:00"} + {":America/New_York" + "2018-03-11 01:59:59" "2018-03-11 02:00:00" "2018-03-11 02:59:59" "2018-03-11 03:00:00" + "2018-11-04 01:59:59" "2018-11-04 02:00:00"} +} +test clock-46.19-1 {free-scan: validation rules: invalid time (DST-hole, out of range in time-zone)} \ + -body { + set res {} + foreach tz $dst_hole_check { set dt [lassign $tz tz]; foreach dt $dt { + lappend res [set v [catch {clock scan $dt -timezone $tz -valid 1} msg]] + if {$v} { lappend res $msg } + }} + set res + } -cleanup { + unset -nocomplain res v dt tz + } -result [lrepeat 4 \ + {*}[list 0 {*}[lrepeat 2 1 {unable to convert input string: invalid time (does not exist in this time-zone)}] 0 0 0]] +test clock-46.19-2 {free-scan: validation rules regression: all scans successful, if -valid 0} \ + -body { + set res {} + set res {} + foreach tz $dst_hole_check { set dt [lassign $tz tz]; foreach dt $dt { + lappend res [set v [catch {clock scan $dt -timezone $tz} msg]] + }} + set res + } -cleanup { + unset -nocomplain res v dt tz + } -result [lrepeat 4 {*}[if {$valid_mode} {list 0 1 1 0 0 0} else {list 0 0 0 0 0 0}]] +test clock-46.19-3 {scan: validation rules: invalid time (DST-hole, out of range in time-zone)} \ + -body { + set res {} + foreach tz $dst_hole_check { set dt [lassign $tz tz]; foreach dt $dt { + lappend res [set v [catch {clock scan $dt -timezone $tz -format "%Y-%m-%d %H:%M:%S" -valid 1} msg]] + if {$v} { lappend res $msg } + }} + set res + } -cleanup { + unset -nocomplain res v dt tz + } -result [lrepeat 4 \ + {*}[list 0 {*}[lrepeat 2 1 {unable to convert input string: invalid time (does not exist in this time-zone)}] 0 0 0]] +test clock-46.19-4 {scan: validation rules regression: all scans successful, if -valid 0} \ + -body { + set res {} + set res {} + foreach tz $dst_hole_check { set dt [lassign $tz tz]; foreach dt $dt { + lappend res [set v [catch {clock scan $dt -timezone $tz -format "%Y-%m-%d %H:%M:%S"} msg]] + }} + set res + } -cleanup { + unset -nocomplain res v dt tz + } -result [lrepeat 4 {*}[if {$valid_mode} {list 0 1 1 0 0 0} else {list 0 0 0 0 0 0}]] +unset -nocomplain dst_hole_check + +proc _invalid_test {args} { + global valid_mode + # ensure validation works TZ independently, since the conversion + # of local time to UTC may adjust date/time tokens, depending on TZ: + set res {} + foreach tz {:GMT :CET {} :Europe/Berlin :localtime} { + foreach {v fmt} $args { + if {$valid_mode} { # globally -valid 1 + lappend res [catch {clock scan $v -format $fmt -timezone $tz} msg] $msg + } else { + lappend res [catch {clock scan $v -format $fmt -valid 1 -timezone $tz} msg] $msg + } + } + } + set res +} +test clock-46.20 {scan: validation rules: invalid time} \ + -body { + # 13:00 am/pm are invalid input strings... + _invalid_test "13:00 am" "%H:%M %p" "13:00 pm" "%H:%M %p" + } -result [lrepeat 10 1 {unable to convert input string: invalid time (hour)}] +test clock-46.21 {scan: validation rules: invalid time} \ + -body { + # invalid minutes in input strings... + _invalid_test "23:70" "%H:%M" "11:80 pm" "%H:%M %p" + } -result [lrepeat 10 1 {unable to convert input string: invalid time (minutes)}] +test clock-46.22 {scan: validation rules: invalid time} \ + -body { + # invalid seconds in input strings... + _invalid_test "23:00:70" "%H:%M:%S" "11:00:80 pm" "%H:%M:%S %p" + } -result [lrepeat 10 1 {unable to convert input string: invalid time}] +test clock-46.23 {scan: validation rules: invalid day} \ + -body { + _invalid_test "29 Feb 2017" "%d %b %Y" "30 Feb 2016" "%d %b %Y" + } -result [lrepeat 10 1 {unable to convert input string: invalid day}] +test clock-46.24 {scan: validation rules: invalid day} \ + -body { + _invalid_test "0 Feb 2017" "%d %b %Y" "00 Feb 2017" "%d %b %Y" + } -result [lrepeat 10 1 {unable to convert input string: invalid day}] +test clock-46.25 {scan: validation rules: invalid month} \ + -body { + _invalid_test "13/13/2017" "%m/%d/%Y" "00/01/2017" "%m/%d/%Y" + } -result [lrepeat 10 1 {unable to convert input string: invalid month}] +test clock-46.26 {scan: validation rules: ambiguous day} \ + -body { + _invalid_test "1970-01-02--004" "%Y-%m-%d--%j" "70-01-02--004" "%y-%m-%d--%j" + } -result [lrepeat 10 1 {unable to convert input string: ambiguous day}] +test clock-46.27 {scan: validation rules: ambiguous year} \ + -body { + _invalid_test "19700106 00W014" "%Y%m%d %gW%V%u" "1970006 00W014" "%Y%j %gW%V%u" + } -result [lrepeat 10 1 {unable to convert input string: ambiguous year}] +test clock-46.28 {scan: validation rules: invalid day of week} \ + -body { + _invalid_test "Sat Jan 02 00:00:00 1970" "%a %b %d %H:%M:%S %Y" + } -result [lrepeat 5 1 {unable to convert input string: invalid day of week}] +test clock-46.29-1 {scan: validation rules: invalid day of year} \ + -body { + _invalid_test "000-2017" "%j-%Y" "366-2017" "%j-%Y" "000-2017" "%j-%G" "366-2017" "%j-%G" + } -result [lrepeat 20 1 {unable to convert input string: invalid day of year}] +test clock-46.29-2 {scan: validation rules: valid day of leap/not leap year} \ + -body { + list [clock format [clock scan "366-2016" -format "%j-%Y" -valid 1 -gmt 1] -format "%d-%m-%Y"] \ + [clock format [clock scan "365-2017" -format "%j-%Y" -valid 1 -gmt 1] -format "%d-%m-%Y"] \ + [clock format [clock scan "366-2016" -format "%j-%G" -valid 1 -gmt 1] -format "%d-%m-%Y"] \ + [clock format [clock scan "365-2017" -format "%j-%G" -valid 1 -gmt 1] -format "%d-%m-%Y"] + } -result {31-12-2016 31-12-2017 31-12-2016 31-12-2017} +test clock-46.30 {scan: validation rules: invalid year} -setup { + set orgcfg [list -min-year [::tcl::unsupported::clock::configure -min-year] -max-year [::tcl::unsupported::clock::configure -max-year] \ + -year-century [::tcl::unsupported::clock::configure -year-century] -century-switch [::tcl::unsupported::clock::configure -century-switch]] + ::tcl::unsupported::clock::configure -min-year 2000 -max-year 2100 -year-century 2000 -century-switch 38 + } -body { + _invalid_test "01-01-70" "%d-%m-%y" "01-01-1870" "%d-%m-%C%y" "01-01-1970" "%d-%m-%Y" + } -result [lrepeat 15 1 {unable to convert input string: invalid year}] -cleanup { + ::tcl::unsupported::clock::configure {*}$orgcfg + unset -nocomplain orgcfg + } +test clock-46.31 {scan: validation rules: invalid iso year} -setup { + set orgcfg [list -min-year [::tcl::unsupported::clock::configure -min-year] -max-year [::tcl::unsupported::clock::configure -max-year] \ + -year-century [::tcl::unsupported::clock::configure -year-century] -century-switch [::tcl::unsupported::clock::configure -century-switch]] + ::tcl::unsupported::clock::configure -min-year 2000 -max-year 2100 -year-century 2000 -century-switch 38 + } -body { + _invalid_test "01-01-70" "%d-%m-%g" "01-01-9870" "%d-%m-%C%g" "01-01-9870" "%d-%m-%G" + } -result [lrepeat 15 1 {unable to convert input string: invalid iso year}] -cleanup { + ::tcl::unsupported::clock::configure {*}$orgcfg + unset -nocomplain orgcfg + } +rename _invalid_test {} + test clock-47.1 {regression test - four-digit time} { clock scan 0012 } [clock scan 0012 -format %H%M] @@ -36954,22 +38180,29 @@ test clock-61.1 {overflow of a wide integer on output} {*}{ -body { clock format 0x8000000000000000 -format %s -gmt true } - -result {integer value too large to represent} + -result {expected integer but got "0x8000000000000000"} -returnCodes error } test clock-61.2 {overflow of a wide integer on output} {*}{ -body { clock format -0x8000000000000001 -format %s -gmt true } - -result {integer value too large to represent} + -result {expected integer but got "-0x8000000000000001"} -returnCodes error } -test clock-61.3 {near-miss overflow of a wide integer on output} { - clock format 0x7fffffffffffffff -format %s -gmt true -} [expr {0x7fffffffffffffff}] -test clock-61.4 {near-miss overflow of a wide integer on output} { - clock format -0x8000000000000000 -format %s -gmt true -} [expr {-0x8000000000000000}] +test clock-61.3 {near-miss overflow of a wide integer on output, very large datetime (upper range)} { + clock format 0x00F0000000000000 -format "%s %Y %EE" -gmt true +} [list [expr 0x00F0000000000000] 2140702833 C.E.] +test clock-61.4 {near-miss overflow of a wide integer on output, very small datetime (lower range)} { + clock format -0x00F0000000000000 -format "%s %Y %EE" -gmt true +} [list [expr -0x00F0000000000000] 2140654939 B.C.E.] + +test clock-61.5 {overflow of possible date-time (upper range)} -body { + clock format 0x00F0000000000001 -gmt true +} -returnCodes error -result {integer value too large to represent} +test clock-61.6 {overflow of possible date-time (lower range)} -body { + clock format -0x00F0000000000001 -gmt true +} -returnCodes error -result {integer value too large to represent} test clock-62.1 {Bug 1902423} {*}{ -setup {::tcl::clock::ClearCaches} @@ -37076,9 +38309,19 @@ test clock-67.5 {Change scan %x output on global locale change [Bug 4a0c163d24]} # cleanup -namespace delete ::testClock ::tcl::clock::ClearCaches +rename test {} +namespace import -force ::tcltest::* +# adjust expected skipped (valid_off is an artificial constraint): +if {$valid_mode && [info exists ::tcltest::skippedBecause(valid_off)]} { + incr ::tcltest::numTests(Total) -$::tcltest::skippedBecause(valid_off) + incr ::tcltest::numTests(Skipped) -$::tcltest::skippedBecause(valid_off) + unset ::tcltest::skippedBecause(valid_off) +} ::tcltest::cleanupTests +namespace delete ::testClock +unset valid_mode + return # Local Variables: diff --git a/unix/Makefile.in b/unix/Makefile.in index 3d8267f..baebacd 100644 --- a/unix/Makefile.in +++ b/unix/Makefile.in @@ -301,7 +301,7 @@ XTTEST_OBJS = xtTestInit.o tclTest.o tclTestObj.o tclTestProcBodyObj.o \ GENERIC_OBJS = regcomp.o regexec.o regfree.o regerror.o tclAlloc.o \ tclArithSeries.o tclAssembly.o tclAsync.o tclBasic.o tclBinary.o \ - tclCkalloc.o tclClock.o tclCmdAH.o tclCmdIL.o tclCmdMZ.o \ + tclCkalloc.o tclClock.o tclClockFmt.o tclCmdAH.o tclCmdIL.o tclCmdMZ.o \ tclCompCmds.o tclCompCmdsGR.o tclCompCmdsSZ.o tclCompExpr.o \ tclCompile.o tclConfig.o tclDate.o tclDictObj.o tclDisassemble.o \ tclEncoding.o tclEnsemble.o \ @@ -313,7 +313,7 @@ GENERIC_OBJS = regcomp.o regexec.o regfree.o regerror.o tclAlloc.o \ tclObj.o tclOptimize.o tclPanic.o tclParse.o tclPathObj.o tclPipe.o \ tclPkg.o tclPkgConfig.o tclPosixStr.o \ tclPreserve.o tclProc.o tclProcess.o tclRegexp.o \ - tclResolve.o tclResult.o tclScan.o tclStringObj.o \ + tclResolve.o tclResult.o tclScan.o tclStringObj.o tclStrIdxTree.o \ tclStrToD.o tclThread.o \ tclThreadAlloc.o tclThreadJoin.o tclThreadStorage.o tclStubInit.o \ tclTimer.o tclTrace.o tclUtf.o tclUtil.o tclVar.o tclZlib.o \ @@ -411,6 +411,7 @@ GENERIC_SRCS = \ $(GENERIC_DIR)/tclBinary.c \ $(GENERIC_DIR)/tclCkalloc.c \ $(GENERIC_DIR)/tclClock.c \ + $(GENERIC_DIR)/tclClockFmt.c \ $(GENERIC_DIR)/tclCmdAH.c \ $(GENERIC_DIR)/tclCmdIL.c \ $(GENERIC_DIR)/tclCmdMZ.c \ @@ -466,6 +467,7 @@ GENERIC_SRCS = \ $(GENERIC_DIR)/tclScan.c \ $(GENERIC_DIR)/tclStubInit.c \ $(GENERIC_DIR)/tclStringObj.c \ + $(GENERIC_DIR)/tclStrIdxTree.c \ $(GENERIC_DIR)/tclStrToD.c \ $(GENERIC_DIR)/tclTest.c \ $(GENERIC_DIR)/tclTestABSList.c \ @@ -1263,6 +1265,7 @@ TRIMHDR = $(GENERIC_DIR)/tclStringTrim.h TCL_LOCATIONS = -DTCL_LIBRARY="\"${TCL_LIBRARY}\"" \ -DTCL_PACKAGE_PATH="\"${TCL_PACKAGE_PATH}\"" +TCLDATEHDR=$(GENERIC_DIR)/tclDate.h $(GENERIC_DIR)/tclStrIdxTree.h regcomp.o: $(REGHDRS) $(GENERIC_DIR)/regcomp.c $(GENERIC_DIR)/regc_lex.c \ $(GENERIC_DIR)/regc_color.c $(GENERIC_DIR)/regc_locale.c \ @@ -1302,9 +1305,12 @@ tclBinary.o: $(GENERIC_DIR)/tclBinary.c tclCkalloc.o: $(GENERIC_DIR)/tclCkalloc.c $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tclCkalloc.c -tclClock.o: $(GENERIC_DIR)/tclClock.c +tclClock.o: $(GENERIC_DIR)/tclClock.c $(TCLDATEHDR) $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tclClock.c +tclClockFmt.o: $(GENERIC_DIR)/tclClockFmt.c $(TCLDATEHDR) + $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tclClockFmt.c + tclCmdAH.o: $(GENERIC_DIR)/tclCmdAH.c $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tclCmdAH.c @@ -1314,7 +1320,7 @@ tclCmdIL.o: $(GENERIC_DIR)/tclCmdIL.c $(TCLREHDRS) tclCmdMZ.o: $(GENERIC_DIR)/tclCmdMZ.c $(TCLREHDRS) $(TRIMHDR) $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tclCmdMZ.c -tclDate.o: $(GENERIC_DIR)/tclDate.c +tclDate.o: $(GENERIC_DIR)/tclDate.c $(TCLDATEHDR) $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tclDate.c tclCompCmds.o: $(GENERIC_DIR)/tclCompCmds.c $(COMPILEHDR) @@ -1535,6 +1541,9 @@ tclScan.o: $(GENERIC_DIR)/tclScan.c tclStringObj.o: $(GENERIC_DIR)/tclStringObj.c $(MATHHDRS) $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tclStringObj.c +tclStrIdxTree.o: $(GENERIC_DIR)/tclStrIdxTree.c $(GENERIC_DIR)/tclStrIdxTree.h $(MATHHDRS) + $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tclStrIdxTree.c + tclStrToD.o: $(GENERIC_DIR)/tclStrToD.c $(MATHHDRS) $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tclStrToD.c diff --git a/win/Makefile.in b/win/Makefile.in index fa88264..1cc996b 100644 --- a/win/Makefile.in +++ b/win/Makefile.in @@ -292,6 +292,7 @@ GENERIC_OBJS = \ tclBinary.$(OBJEXT) \ tclCkalloc.$(OBJEXT) \ tclClock.$(OBJEXT) \ + tclClockFmt.$(OBJEXT) \ tclCmdAH.$(OBJEXT) \ tclCmdIL.$(OBJEXT) \ tclCmdMZ.$(OBJEXT) \ @@ -355,6 +356,7 @@ GENERIC_OBJS = \ tclResult.$(OBJEXT) \ tclScan.$(OBJEXT) \ tclStringObj.$(OBJEXT) \ + tclStrIdxTree.$(OBJEXT) \ tclStrToD.$(OBJEXT) \ tclStubInit.$(OBJEXT) \ tclThread.$(OBJEXT) \ diff --git a/win/makefile.vc b/win/makefile.vc index aa122c3..261e0a2 100644 --- a/win/makefile.vc +++ b/win/makefile.vc @@ -254,6 +254,7 @@ COREOBJS = \ $(TMP_DIR)\tclBinary.obj \
$(TMP_DIR)\tclCkalloc.obj \
$(TMP_DIR)\tclClock.obj \
+ $(TMP_DIR)\tclClockFmt.obj \
$(TMP_DIR)\tclCmdAH.obj \
$(TMP_DIR)\tclCmdIL.obj \
$(TMP_DIR)\tclCmdMZ.obj \
@@ -317,6 +318,7 @@ COREOBJS = \ $(TMP_DIR)\tclResult.obj \
$(TMP_DIR)\tclScan.obj \
$(TMP_DIR)\tclStringObj.obj \
+ $(TMP_DIR)\tclStrIdxTree.obj \
$(TMP_DIR)\tclStrToD.obj \
$(TMP_DIR)\tclStubInit.obj \
$(TMP_DIR)\tclThread.obj \
|