Index: doc/clock.n ================================================================== --- doc/clock.n +++ doc/clock.n @@ -85,10 +85,19 @@ exactly 86400 seconds. Tcl responds to leap seconds by speeding or 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, \fBdays\fR, \fBweeks\fR, \fBmonths\fR, or \fByears\fR, or any unique prefix of such a word. Used in conjunction with \fIcount\fR to identify an interval of time, for example, \fI3 seconds\fR or @@ -387,12 +396,16 @@ preprocessed format string. In order of preference: .IP [1] 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; or ISO8601 fiscal year, week of year, and day of week; those groups are combined and used to determine the date. If more than one complete @@ -539,10 +552,40 @@ 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. .TP +\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. +.TP +\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. +.TP +\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. +.TP \fB%Ex\fR On output, produces a locale-dependent representation of the date in the locale's alternative calendar. On input, matches whatever \fB%Ex\fR produces. The locale's alternative calendar need not be the Gregorian calendar. @@ -588,11 +631,11 @@ \fB%j\fR On output, produces a three-digit number giving the day of the year (001-366). On input, accepts such a number. .TP \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 Julian calendar. The epoch time of 1 January 1970 corresponds to Julian Day Number 2440588. @@ -733,17 +776,19 @@ .TP \fB%z\fR 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). .TP \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 use by computers use numeric time zones instead. .TP @@ -939,14 +984,32 @@ specified, and no absolute or relative time is given, midnight is 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 clock, date, time .SH "COPYRIGHT" Copyright \(co 2004 Kevin B. Kenny . All rights reserved. '\" Local Variables: '\" mode: nroff '\" End: Index: generic/tclClock.c ================================================================== --- generic/tclClock.c +++ generic/tclClock.c @@ -6,40 +6,28 @@ * Lehenbauer. * * Copyright (c) 1991-1995 Karl Lehenbauer & Mark Diekhans. * Copyright (c) 1995 Sun Microsystems, Inc. * Copyright (c) 2004 by Kevin B. Kenny. All rights reserved. + * 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" /* * Windows has mktime. The configurators do not check. */ #ifdef _WIN32 #define HAVE_MKTIME 1 #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] = { @@ -53,74 +41,17 @@ /* * 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" -}; - -/* - * Structure containing the client data for [clock] - */ - -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 TclDateFields { - Tcl_WideInt seconds; /* Time expressed in seconds from the Posix - * epoch */ - Tcl_WideInt localSeconds; /* Local time expressed in nominal seconds - * from the Posix epoch */ - int tzOffset; /* Time zone offset in seconds east of - * Greenwich */ - Tcl_Obj *tzName; /* Time zone name */ - int julianDay; /* Julian Day Number in local time zone */ - enum {BCE=1, CE=0} era; /* Era */ - int gregorian; /* Flag == 1 if the date is Gregorian */ - int year; /* Year of the era */ - int dayOfYear; /* Day of the year (1 January == 1) */ - int month; /* Month number */ - int dayOfMonth; /* Day of the month */ - int iso8601Year; /* ISO8601 week-based year */ - int iso8601Week; /* ISO8601 week number */ - int dayOfWeek; /* Day of the week */ -} TclDateFields; +CLOCK_LITERAL_ARRAY(Literals); + +/* 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_"); + static const char *const eras[] = { "CE", "BCE", NULL }; /* * Thread specific data block holding a 'struct tm' for the 'gmtime' and * 'localtime' library calls. @@ -137,37 +68,38 @@ /* * Function prototypes for local procedures in this file: */ -static int ConvertUTCToLocal(Tcl_Interp *, - TclDateFields *, Tcl_Obj *, int); static int ConvertUTCToLocalUsingTable(Tcl_Interp *, - TclDateFields *, int, Tcl_Obj *const[]); + TclDateFields *, int, 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(ClientData clientData, Tcl_Interp *, + TclDateFields *, Tcl_Obj *timezoneObj, int); static int ConvertLocalToUTCUsingTable(Tcl_Interp *, - TclDateFields *, int, 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, - int, Tcl_Obj *const *); +static int ClockConfigureObjCmd(ClientData 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 int WeekdayOnOrBefore(int, int); +static Tcl_WideInt WeekdayOnOrBefore(int, Tcl_WideInt); static int ClockClicksObjCmd( ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]); static int ClockConvertlocaltoutcObjCmd( ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]); + +static int ClockGetDateFields(ClientData clientData, + Tcl_Interp *interp, TclDateFields *fields, + Tcl_Obj *timezoneObj, int changeover); static int ClockGetdatefieldsObjCmd( ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]); static int ClockGetjuliandayfromerayearmonthdayObjCmd( ClientData clientData, Tcl_Interp *interp, @@ -182,44 +114,74 @@ ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]); static int ClockMillisecondsObjCmd( ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]); -static int ClockParseformatargsObjCmd( - ClientData clientData, Tcl_Interp *interp, - int objc, Tcl_Obj *const objv[]); static int ClockSecondsObjCmd( ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]); +static int ClockFormatObjCmd( + ClientData clientData, Tcl_Interp *interp, + int objc, Tcl_Obj *const objv[]); +static int ClockScanObjCmd( + ClientData clientData, Tcl_Interp *interp, + int objc, Tcl_Obj *const objv[]); +static int ClockScanCommit( + DateInfo *info, + ClockFmtScnCmdArgs *opts); +static int ClockFreeScan( + DateInfo *info, + Tcl_Obj *strObj, ClockFmtScnCmdArgs *opts); +static int ClockCalcRelTime( + DateInfo *info); +static int ClockAddObjCmd( + ClientData clientData, Tcl_Interp *interp, + int objc, Tcl_Obj *const objv[]); +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(ClientData); +static int ClockSafeCatchCmd( + ClientData clientData, Tcl_Interp *interp, + int objc, Tcl_Obj *const objv[]); /* * Structure containing description of "native" clock commands to create. */ struct ClockCommand { const char *name; /* The tail of the command name. The full name * is "::tcl::clock::". 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. */ + ClientData 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 }, - { "GetJulianDayFromEraYearMonthDay", - ClockGetjuliandayfromerayearmonthdayObjCmd }, - { "GetJulianDayFromEraYearWeekDay", - ClockGetjuliandayfromerayearweekdayObjCmd }, - { "ParseFormatArgs", ClockParseformatargsObjCmd }, - { NULL, NULL } + {"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)}, + {"configure", ClockConfigureObjCmd, NULL, NULL}, + {"ConvertLocalToUTC", ClockConvertlocaltoutcObjCmd, NULL, NULL}, + {"GetDateFields", ClockGetdatefieldsObjCmd, NULL, NULL}, + {"GetJulianDayFromEraYearMonthDay", + ClockGetjuliandayfromerayearmonthdayObjCmd, NULL, NULL}, + {"GetJulianDayFromEraYearWeekDay", + ClockGetjuliandayfromerayearweekdayObjCmd, NULL, NULL}, + {"catch", ClockSafeCatchCmd, TclCompileBasicMin1ArgCmd, NULL}, + {NULL, NULL, NULL, NULL} }; /* *---------------------------------------------------------------------- * @@ -244,26 +206,14 @@ { const struct ClockCommand *clockCmdPtr; 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. */ @@ -277,31 +227,1170 @@ data = (ClockClientData *)ckalloc(sizeof(ClockClientData)); data->refCount = 0; data->literals = (Tcl_Obj **)ckalloc(LIT__END * sizeof(Tcl_Obj*)); for (i = 0; i < LIT__END; ++i) { - data->literals[i] = Tcl_NewStringObj(literals[i], -1); - Tcl_IncrRefCount(data->literals[i]); + Tcl_InitObjRef(data->literals[i], Tcl_NewStringObj(Literals[i], -1)); } + data->mcLiterals = NULL; + data->mcLitIdxs = NULL; + data->mcDicts = NULL; + data->lastTZEpoch = 0; + data->currentYearCentury = ClockDefaultYearCentury; + data->yearOfCenturySwitch = ClockDefaultCenturySwitch; + data->validMinYear = INT_MIN; + data->validMaxYear = INT_MAX; + /* corresponds max of JDN in sqlite - 9999-12-31 23:59:59 per default */ + data->maxJDN = 5373484.499999994; + + data->systemTimeZone = NULL; + data->systemSetupTZData = NULL; + data->gmtSetupTimeZoneUnnorm = NULL; + data->gmtSetupTimeZone = NULL; + data->gmtSetupTZData = NULL; + data->gmtTZName = NULL; + data->lastSetupTimeZoneUnnorm = NULL; + data->lastSetupTimeZone = NULL; + data->lastSetupTZData = NULL; + data->prevSetupTimeZoneUnnorm = NULL; + data->prevSetupTimeZone = NULL; + data->prevSetupTZData = NULL; + + data->defaultLocale = NULL; + data->defaultLocaleDict = NULL; + data->currentLocale = NULL; + data->currentLocaleDict = NULL; + data->lastUsedLocaleUnnorm = NULL; + data->lastUsedLocale = NULL; + data->lastUsedLocaleDict = NULL; + data->prevUsedLocaleUnnorm = NULL; + data->prevUsedLocale = NULL; + data->prevUsedLocaleDict = NULL; + + data->lastBase.timezoneObj = NULL; + + memset(&data->lastTZOffsCache, 0, sizeof(data->lastTZOffsCache)); + + data->defFlags = 0; /* * Install the commands. - * TODO - Let Tcl_MakeEnsemble do this? */ #define TCL_CLOCK_PREFIX_LEN 14 /* == strlen("::tcl::clock::") */ memcpy(cmdName, "::tcl::clock::", TCL_CLOCK_PREFIX_LEN); for (clockCmdPtr=clockCommands ; clockCmdPtr->name!=NULL ; clockCmdPtr++) { + ClientData clientData; + strcpy(cmdName + TCL_CLOCK_PREFIX_LEN, clockCmdPtr->name); - data->refCount++; - Tcl_CreateObjCommand(interp, cmdName, clockCmdPtr->objCmdProc, data, - ClockDeleteCmdProc); - } - - /* Make the clock ensemble */ - - TclMakeEnsemble(interp, "clock", clockImplMap); + 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; + } +} + +/* + *---------------------------------------------------------------------- + * + * ClockConfigureClear -- + * + * Clean up cached resp. run-time storages used in clock commands. + * + * Shared usage for clean-up (ClockDeleteCmdProc) and "configure -clear". + * + * Results: + * None. + * + *---------------------------------------------------------------------- + */ + +static void +ClockConfigureClear( + ClockClientData *data) +{ + ClockFrmScnClearCaches(); + + data->lastTZEpoch = 0; + Tcl_UnsetObjRef(data->systemTimeZone); + Tcl_UnsetObjRef(data->systemSetupTZData); + Tcl_UnsetObjRef(data->gmtSetupTimeZoneUnnorm); + Tcl_UnsetObjRef(data->gmtSetupTimeZone); + Tcl_UnsetObjRef(data->gmtSetupTZData); + Tcl_UnsetObjRef(data->gmtTZName); + Tcl_UnsetObjRef(data->lastSetupTimeZoneUnnorm); + Tcl_UnsetObjRef(data->lastSetupTimeZone); + Tcl_UnsetObjRef(data->lastSetupTZData); + Tcl_UnsetObjRef(data->prevSetupTimeZoneUnnorm); + Tcl_UnsetObjRef(data->prevSetupTimeZone); + Tcl_UnsetObjRef(data->prevSetupTZData); + + Tcl_UnsetObjRef(data->defaultLocale); + data->defaultLocaleDict = NULL; + Tcl_UnsetObjRef(data->currentLocale); + data->currentLocaleDict = NULL; + Tcl_UnsetObjRef(data->lastUsedLocaleUnnorm); + Tcl_UnsetObjRef(data->lastUsedLocale); + data->lastUsedLocaleDict = NULL; + Tcl_UnsetObjRef(data->prevUsedLocaleUnnorm); + Tcl_UnsetObjRef(data->prevUsedLocale); + data->prevUsedLocaleDict = NULL; + + Tcl_UnsetObjRef(data->lastBase.timezoneObj); + + Tcl_UnsetObjRef(data->lastTZOffsCache[0].timezoneObj); + Tcl_UnsetObjRef(data->lastTZOffsCache[0].tzName); + Tcl_UnsetObjRef(data->lastTZOffsCache[1].timezoneObj); + Tcl_UnsetObjRef(data->lastTZOffsCache[1].tzName); + + Tcl_UnsetObjRef(data->mcDicts); +} + +/* + *---------------------------------------------------------------------- + * + * ClockDeleteCmdProc -- + * + * Remove a reference to the clock client data, and clean up memory + * when it's all gone. + * + * Results: + * None. + * + *---------------------------------------------------------------------- + */ +static void +ClockDeleteCmdProc( + ClientData 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); + + ckfree(data->literals); + ckfree(data); + } +} + +/* + *---------------------------------------------------------------------- + * + * SavePrevTimezoneObj -- + * + * Used to store previously used/cached time zone (makes it reusable). + * + * This enables faster switch between time zones (e. g. to convert from one to another). + * + * Results: + * None. + * + *---------------------------------------------------------------------- + */ + +static inline void +SavePrevTimezoneObj( + ClockClientData *dataPtr) /* Client data containing literal pool */ +{ + Tcl_Obj *timezoneObj = dataPtr->lastSetupTimeZone; + if (timezoneObj && timezoneObj != dataPtr->prevSetupTimeZone) { + Tcl_SetObjRef(dataPtr->prevSetupTimeZoneUnnorm, dataPtr->lastSetupTimeZoneUnnorm); + Tcl_SetObjRef(dataPtr->prevSetupTimeZone, timezoneObj); + Tcl_SetObjRef(dataPtr->prevSetupTZData, dataPtr->lastSetupTZData); + } +} + +/* + *---------------------------------------------------------------------- + * + * NormTimezoneObj -- + * + * Normalizes the timezone object (used for caching puposes). + * + * If already cached time zone could be found, returns this + * object (last setup or last used, system (current) or gmt). + * + * Results: + * Normalized tcl object pointer. + * + *---------------------------------------------------------------------- + */ + +static Tcl_Obj * +NormTimezoneObj( + ClockClientData *dataPtr, /* Client data containing literal pool */ + Tcl_Obj *timezoneObj, /* Name of zone to find */ + int *loaded) /* Used to recognized TZ was loaded */ +{ + const char *tz; + + *loaded = 1; + if ( timezoneObj == dataPtr->lastSetupTimeZoneUnnorm + && dataPtr->lastSetupTimeZone != NULL + ) { + return dataPtr->lastSetupTimeZone; + } + if ( timezoneObj == dataPtr->prevSetupTimeZoneUnnorm + && dataPtr->prevSetupTimeZone != NULL + ) { + return dataPtr->prevSetupTimeZone; + } + if (timezoneObj == dataPtr->gmtSetupTimeZoneUnnorm + && dataPtr->gmtSetupTimeZone != NULL + ) { + return dataPtr->literals[LIT_GMT]; + } + if ( timezoneObj == dataPtr->lastSetupTimeZone + || timezoneObj == dataPtr->prevSetupTimeZone + || timezoneObj == dataPtr->gmtSetupTimeZone + || timezoneObj == dataPtr->systemTimeZone + ) { + return timezoneObj; + } + + tz = TclGetString(timezoneObj); + if (dataPtr->lastSetupTimeZone != NULL && + strcmp(tz, TclGetString(dataPtr->lastSetupTimeZone)) == 0 + ) { + Tcl_SetObjRef(dataPtr->lastSetupTimeZoneUnnorm, timezoneObj); + return dataPtr->lastSetupTimeZone; + } + if (dataPtr->prevSetupTimeZone != NULL && + strcmp(tz, TclGetString(dataPtr->prevSetupTimeZone)) == 0 + ) { + Tcl_SetObjRef(dataPtr->prevSetupTimeZoneUnnorm, timezoneObj); + return dataPtr->prevSetupTimeZone; + } + if (dataPtr->systemTimeZone != NULL && + strcmp(tz, TclGetString(dataPtr->systemTimeZone)) == 0 + ) { + return dataPtr->systemTimeZone; + } + if (strcmp(tz, Literals[LIT_GMT]) == 0) { + Tcl_SetObjRef(dataPtr->gmtSetupTimeZoneUnnorm, timezoneObj); + if (dataPtr->gmtSetupTimeZone == NULL) { + *loaded = 0; + } + return dataPtr->literals[LIT_GMT]; + } + /* unknown/unloaded tz - recache/revalidate later as last-setup if needed */ + *loaded = 0; + return timezoneObj; +} + +/* + *---------------------------------------------------------------------- + * + * ClockGetSystemLocale -- + * + * Returns system locale. + * + * Executes ::tcl::clock::GetSystemLocale in given interpreter. + * + * Results: + * Returns system locale tcl object. + * + *---------------------------------------------------------------------- + */ + +static inline Tcl_Obj * +ClockGetSystemLocale( + ClockClientData *dataPtr, /* Opaque pointer to literal pool, etc. */ + Tcl_Interp *interp) /* Tcl interpreter */ +{ + if (Tcl_EvalObjv(interp, 1, &dataPtr->literals[LIT_GETSYSTEMLOCALE], 0) != TCL_OK) { + return NULL; + } + + return Tcl_GetObjResult(interp); +} +/* + *---------------------------------------------------------------------- + * + * ClockGetCurrentLocale -- + * + * Returns current locale. + * + * Executes ::tcl::clock::mclocale in given interpreter. + * + * Results: + * Returns current locale tcl object. + * + *---------------------------------------------------------------------- + */ + +static inline Tcl_Obj * +ClockGetCurrentLocale( + ClockClientData *dataPtr, /* Client data containing literal pool */ + Tcl_Interp *interp) /* Tcl interpreter */ +{ + if (Tcl_EvalObjv(interp, 1, &dataPtr->literals[LIT_GETCURRENTLOCALE], 0) != TCL_OK) { + return NULL; + } + + Tcl_SetObjRef(dataPtr->currentLocale, Tcl_GetObjResult(interp)); + dataPtr->currentLocaleDict = NULL; + Tcl_ResetResult(interp); + + return dataPtr->currentLocale; +} + +/* + *---------------------------------------------------------------------- + * + * SavePrevLocaleObj -- + * + * Used to store previously used/cached locale (makes it reusable). + * + * This enables faster switch between locales (e. g. to convert from one to another). + * + * Results: + * None. + * + *---------------------------------------------------------------------- + */ + +static inline void +SavePrevLocaleObj( + ClockClientData *dataPtr) /* Client data containing literal pool */ +{ + Tcl_Obj *localeObj = dataPtr->lastUsedLocale; + if (localeObj && localeObj != dataPtr->prevUsedLocale) { + Tcl_SetObjRef(dataPtr->prevUsedLocaleUnnorm, dataPtr->lastUsedLocaleUnnorm); + Tcl_SetObjRef(dataPtr->prevUsedLocale, localeObj); + /* mcDicts owns reference to dict */ + dataPtr->prevUsedLocaleDict = dataPtr->lastUsedLocaleDict; + } +} + +/* + *---------------------------------------------------------------------- + * + * NormLocaleObj -- + * + * Normalizes the locale object (used for caching puposes). + * + * If already cached locale could be found, returns this + * object (current, system (OS) or last used locales). + * + * Results: + * Normalized tcl object pointer. + * + *---------------------------------------------------------------------- + */ + +static Tcl_Obj * +NormLocaleObj( + ClockClientData *dataPtr, /* Client data containing literal pool */ + Tcl_Interp *interp, /* Tcl interpreter */ + Tcl_Obj *localeObj, + Tcl_Obj **mcDictObj) +{ + const char *loc, *loc2; + if ( localeObj == NULL + || localeObj == dataPtr->literals[LIT_C] + || localeObj == dataPtr->defaultLocale + ) { + *mcDictObj = dataPtr->defaultLocaleDict; + return dataPtr->defaultLocale ? + dataPtr->defaultLocale : dataPtr->literals[LIT_C]; + } + if ( localeObj == dataPtr->currentLocale + || localeObj == dataPtr->literals[LIT_CURRENT] + ) { + if (dataPtr->currentLocale == NULL) { + ClockGetCurrentLocale(dataPtr, interp); + } + *mcDictObj = dataPtr->currentLocaleDict; + return dataPtr->currentLocale; + } + if ( localeObj == dataPtr->lastUsedLocale + || localeObj == dataPtr->lastUsedLocaleUnnorm + ) { + *mcDictObj = dataPtr->lastUsedLocaleDict; + return dataPtr->lastUsedLocale; + } + if ( localeObj == dataPtr->prevUsedLocale + || localeObj == dataPtr->prevUsedLocaleUnnorm + ) { + *mcDictObj = dataPtr->prevUsedLocaleDict; + return dataPtr->prevUsedLocale; + } + + loc = TclGetString(localeObj); + if ( dataPtr->currentLocale != NULL + && ( localeObj == dataPtr->currentLocale + || (localeObj->length == dataPtr->currentLocale->length + && strcasecmp(loc, TclGetString(dataPtr->currentLocale)) == 0 + ) + ) + ) { + *mcDictObj = dataPtr->currentLocaleDict; + return dataPtr->currentLocale; + } + if ( dataPtr->lastUsedLocale != NULL + && ( localeObj == dataPtr->lastUsedLocale + || (localeObj->length == dataPtr->lastUsedLocale->length + && strcasecmp(loc, TclGetString(dataPtr->lastUsedLocale)) == 0 + ) + ) + ) { + *mcDictObj = dataPtr->lastUsedLocaleDict; + Tcl_SetObjRef(dataPtr->lastUsedLocaleUnnorm, localeObj); + return dataPtr->lastUsedLocale; + } + if ( dataPtr->prevUsedLocale != NULL + && ( localeObj == dataPtr->prevUsedLocale + || (localeObj->length == dataPtr->prevUsedLocale->length + && strcasecmp(loc, TclGetString(dataPtr->prevUsedLocale)) == 0 + ) + ) + ) { + *mcDictObj = dataPtr->prevUsedLocaleDict; + Tcl_SetObjRef(dataPtr->prevUsedLocaleUnnorm, localeObj); + return dataPtr->prevUsedLocale; + } + if ( + (localeObj->length == 1 /* C */ + && strcasecmp(loc, Literals[LIT_C]) == 0) + || (dataPtr->defaultLocale && (loc2 = TclGetString(dataPtr->defaultLocale)) + && localeObj->length == dataPtr->defaultLocale->length + && strcasecmp(loc, loc2) == 0) + ) { + *mcDictObj = dataPtr->defaultLocaleDict; + return dataPtr->defaultLocale ? + dataPtr->defaultLocale : dataPtr->literals[LIT_C]; + } + if ( localeObj->length == 7 /* current */ + && strcasecmp(loc, Literals[LIT_CURRENT]) == 0 + ) { + if (dataPtr->currentLocale == NULL) { + ClockGetCurrentLocale(dataPtr, interp); + } + *mcDictObj = dataPtr->currentLocaleDict; + return dataPtr->currentLocale; + } + if ( + (localeObj->length == 6 /* system */ + && strcasecmp(loc, Literals[LIT_SYSTEM]) == 0) + ) { + SavePrevLocaleObj(dataPtr); + Tcl_SetObjRef(dataPtr->lastUsedLocaleUnnorm, localeObj); + localeObj = ClockGetSystemLocale(dataPtr, interp); + Tcl_SetObjRef(dataPtr->lastUsedLocale, localeObj); + *mcDictObj = NULL; + return localeObj; + } + *mcDictObj = NULL; + return localeObj; +} + +/* + *---------------------------------------------------------------------- + * + * ClockMCDict -- + * + * Retrieves a localized storage dictionary object for the given + * locale object. + * + * This corresponds with call `::tcl::clock::mcget locale`. + * Cached representation stored in options (for further access). + * + * Results: + * Tcl-object contains smart reference to msgcat dictionary. + * + *---------------------------------------------------------------------- + */ + +Tcl_Obj * +ClockMCDict(ClockFmtScnCmdArgs *opts) +{ + ClockClientData *dataPtr = (ClockClientData *)opts->clientData; + + /* if dict not yet retrieved */ + if (opts->mcDictObj == NULL) { + + /* if locale was not yet used */ + if ( !(opts->flags & CLF_LOCALE_USED) ) { + + opts->localeObj = NormLocaleObj((ClockClientData *)opts->clientData, opts->interp, + opts->localeObj, &opts->mcDictObj); + + if (opts->localeObj == NULL) { + Tcl_SetObjResult(opts->interp, + Tcl_NewStringObj("locale not specified and no default locale set", -1)); + Tcl_SetErrorCode(opts->interp, "CLOCK", "badOption", (char *)NULL); + return NULL; + } + opts->flags |= CLF_LOCALE_USED; + + /* check locale literals already available (on demand creation) */ + if (dataPtr->mcLiterals == NULL) { + int i; + dataPtr->mcLiterals = (Tcl_Obj **)ckalloc(MCLIT__END * sizeof(Tcl_Obj*)); + for (i = 0; i < MCLIT__END; ++i) { + Tcl_InitObjRef(dataPtr->mcLiterals[i], + Tcl_NewStringObj(MsgCtLiterals[i], -1)); + } + } + } + + /* check or obtain mcDictObj (be sure it's modifiable) */ + if (opts->mcDictObj == NULL || opts->mcDictObj->refCount > 1) { + int ref = 1; + + /* first try to find locale catalog dict */ + if (dataPtr->mcDicts == NULL) { + Tcl_SetObjRef(dataPtr->mcDicts, Tcl_NewDictObj()); + } + Tcl_DictObjGet(NULL, dataPtr->mcDicts, + opts->localeObj, &opts->mcDictObj); + + if (opts->mcDictObj == NULL) { + /* get msgcat dictionary - ::tcl::clock::mcget locale */ + Tcl_Obj *callargs[2]; + + callargs[0] = dataPtr->literals[LIT_MCGET]; + callargs[1] = opts->localeObj; + + if (Tcl_EvalObjv(opts->interp, 2, callargs, 0) != TCL_OK) { + return NULL; + } + + opts->mcDictObj = Tcl_GetObjResult(opts->interp); + Tcl_ResetResult(opts->interp); + ref = 0; /* new object is not yet referenced */ + } + + /* be sure that object reference doesn't increase (dict changeable) */ + if (opts->mcDictObj->refCount > ref) { + /* smart reference (shared dict as object with no ref-counter) */ + opts->mcDictObj = TclDictObjSmartRef(opts->interp, + opts->mcDictObj); + } + + /* create exactly one reference to catalog / make it searchable for future */ + Tcl_DictObjPut(NULL, dataPtr->mcDicts, opts->localeObj, + opts->mcDictObj); + + if ( opts->localeObj == dataPtr->literals[LIT_C] + || opts->localeObj == dataPtr->defaultLocale + ) { + dataPtr->defaultLocaleDict = opts->mcDictObj; + } + if ( opts->localeObj == dataPtr->currentLocale ) { + dataPtr->currentLocaleDict = opts->mcDictObj; + } else if ( opts->localeObj == dataPtr->lastUsedLocale ) { + dataPtr->lastUsedLocaleDict = opts->mcDictObj; + } else { + SavePrevLocaleObj(dataPtr); + Tcl_SetObjRef(dataPtr->lastUsedLocale, opts->localeObj); + Tcl_UnsetObjRef(dataPtr->lastUsedLocaleUnnorm); + dataPtr->lastUsedLocaleDict = opts->mcDictObj; + } + } + } + + return opts->mcDictObj; +} + +/* + *---------------------------------------------------------------------- + * + * ClockMCGet -- + * + * Retrieves a msgcat value for the given literal integer mcKey + * from localized storage (corresponding given locale object) + * by mcLiterals[mcKey] (e. g. MONTHS_FULL). + * + * Results: + * Tcl-object contains localized value. + * + *---------------------------------------------------------------------- + */ + +Tcl_Obj * +ClockMCGet( + ClockFmtScnCmdArgs *opts, + int mcKey) +{ + ClockClientData *dataPtr = (ClockClientData *)opts->clientData; + + Tcl_Obj *valObj = NULL; + + if (opts->mcDictObj == NULL) { + ClockMCDict(opts); + if (opts->mcDictObj == NULL) + return NULL; + } + + Tcl_DictObjGet(opts->interp, opts->mcDictObj, + dataPtr->mcLiterals[mcKey], &valObj); + + return valObj; /* or NULL in obscure case if Tcl_DictObjGet failed */ +} + +/* + *---------------------------------------------------------------------- + * + * ClockMCGetIdx -- + * + * Retrieves an indexed msgcat value for the given literal integer mcKey + * from localized storage (corresponding given locale object) + * by mcLitIdxs[mcKey] (e. g. _IDX_MONTHS_FULL). + * + * Results: + * Tcl-object contains localized indexed value. + * + *---------------------------------------------------------------------- + */ + +MODULE_SCOPE Tcl_Obj * +ClockMCGetIdx( + ClockFmtScnCmdArgs *opts, + int mcKey) +{ + ClockClientData *dataPtr = (ClockClientData *)opts->clientData; + + Tcl_Obj *valObj = NULL; + + if (opts->mcDictObj == NULL) { + ClockMCDict(opts); + if (opts->mcDictObj == NULL) + return NULL; + } + + /* try to get indices object */ + if (dataPtr->mcLitIdxs == NULL) { + return NULL; + } + + if (Tcl_DictObjGet(NULL, opts->mcDictObj, + dataPtr->mcLitIdxs[mcKey], &valObj) != TCL_OK + ) { + return NULL; + } + + 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 **)ckalloc(MCLIT__END * sizeof(Tcl_Obj*)); + for (i = 0; i < MCLIT__END; ++i) { + Tcl_InitObjRef(dataPtr->mcLitIdxs[i], + Tcl_NewStringObj(MsgCtLitIdxs[i], -1)); + } + } + + return Tcl_DictObjPut(opts->interp, opts->mcDictObj, + dataPtr->mcLitIdxs[mcKey], valObj); +} + +static void +TimezoneLoaded( + ClockClientData *dataPtr, + Tcl_Obj *timezoneObj, /* Name of zone was loaded */ + Tcl_Obj *tzUnnormObj) /* Name of zone was loaded */ +{ + /* last setup zone loaded */ + if (dataPtr->lastSetupTimeZone != timezoneObj) { + SavePrevTimezoneObj(dataPtr); + Tcl_SetObjRef(dataPtr->lastSetupTimeZone, timezoneObj); + Tcl_UnsetObjRef(dataPtr->lastSetupTZData); + } + Tcl_SetObjRef(dataPtr->lastSetupTimeZoneUnnorm, tzUnnormObj); + + /* mark GMT zone loaded */ + if ( dataPtr->gmtSetupTimeZone == NULL + && timezoneObj == dataPtr->literals[LIT_GMT] + ) { + Tcl_SetObjRef(dataPtr->gmtSetupTimeZone, + dataPtr->literals[LIT_GMT]); + } +} +/* + *---------------------------------------------------------------------- + * + * ClockConfigureObjCmd -- + * + * This function is invoked to process the Tcl "clock configure" command. + * + * Usage: + * ::tcl::clock::configure ?-option ?value?? + * + * Results: + * Returns a standard Tcl result. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static int +ClockConfigureObjCmd( + ClientData 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", + 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 + }; + int optionIndex; /* Index of an option. */ + int i; + + for (i = 1; i < objc; i++) { + if (Tcl_GetIndexFromObj(interp, objv[i++], options, + "option", 0, &optionIndex) != TCL_OK) { + Tcl_SetErrorCode(interp, "CLOCK", "badOption", + Tcl_GetString(objv[i-1]), (char *)NULL); + return TCL_ERROR; + } + switch (optionIndex) { + case CLOCK_SYSTEM_TZ: + if (1) { + /* validate current tz-epoch */ + size_t lastTZEpoch = TzsetIfNecessary(); + if (i < objc) { + if (dataPtr->systemTimeZone != objv[i]) { + Tcl_SetObjRef(dataPtr->systemTimeZone, objv[i]); + Tcl_UnsetObjRef(dataPtr->systemSetupTZData); + } + dataPtr->lastTZEpoch = lastTZEpoch; + } + if (i+1 >= objc && dataPtr->systemTimeZone != NULL + && dataPtr->lastTZEpoch == lastTZEpoch) { + Tcl_SetObjResult(interp, dataPtr->systemTimeZone); + } + } + break; + case CLOCK_SETUP_TZ: + if (i < objc) { + int loaded; + Tcl_Obj *timezoneObj = NormTimezoneObj(dataPtr, objv[i], &loaded); + if (!loaded) { + TimezoneLoaded(dataPtr, timezoneObj, objv[i]); + } + Tcl_SetObjResult(interp, timezoneObj); + } + else + if (i+1 >= objc && dataPtr->lastSetupTimeZone != NULL) { + Tcl_SetObjResult(interp, dataPtr->lastSetupTimeZone); + } + break; + case CLOCK_DEFAULT_LOCALE: + if (i < objc) { + if (dataPtr->defaultLocale != objv[i]) { + Tcl_SetObjRef(dataPtr->defaultLocale, objv[i]); + dataPtr->defaultLocaleDict = NULL; + } + } + if (i+1 >= objc) { + Tcl_SetObjResult(interp, dataPtr->defaultLocale ? + dataPtr->defaultLocale : dataPtr->literals[LIT_C]); + } + break; + case CLOCK_CURRENT_LOCALE: + if (i < objc) { + if (dataPtr->currentLocale != objv[i]) { + Tcl_SetObjRef(dataPtr->currentLocale, objv[i]); + dataPtr->currentLocaleDict = NULL; + } + } + if (i+1 >= objc && dataPtr->currentLocale != NULL) { + Tcl_SetObjResult(interp, dataPtr->currentLocale); + } + break; + case CLOCK_YEAR_CENTURY: + if (i < objc) { + int year; + if (TclGetIntFromObj(interp, objv[i], &year) != TCL_OK) { + return TCL_ERROR; + } + dataPtr->currentYearCentury = year; + if (i+1 >= objc) { + Tcl_SetObjResult(interp, objv[i]); + } + continue; + } + if (i+1 >= objc) { + Tcl_SetObjResult(interp, + Tcl_NewIntObj(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_NewIntObj(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_NewIntObj(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_NewIntObj(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_NewIntObj(dataPtr->defFlags & CLF_VALIDATE ? 1 : 0)); + } + break; + case CLOCK_CLEAR_CACHE: + ClockConfigureClear(dataPtr); + 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( + ClientData clientData, /* Opaque pointer to literal pool, etc. */ + Tcl_Interp *interp, /* Tcl interpreter */ + Tcl_Obj *timezoneObj) /* Name of the timezone */ +{ + ClockClientData *dataPtr = (ClockClientData *)clientData; + Tcl_Obj *ret, **out = NULL; + + /* if cached (if already setup this one) */ + if ( timezoneObj == dataPtr->lastSetupTimeZone + || timezoneObj == dataPtr->lastSetupTimeZoneUnnorm + ) { + if (dataPtr->lastSetupTZData != NULL) { + return dataPtr->lastSetupTZData; + } + out = &dataPtr->lastSetupTZData; + } + /* differentiate GMT and system zones, because used often */ + /* simple caching, because almost used the tz-data of last timezone + */ + if (timezoneObj == dataPtr->systemTimeZone) { + if (dataPtr->systemSetupTZData != NULL) { + return dataPtr->systemSetupTZData; + } + out = &dataPtr->systemSetupTZData; + } + else + if ( timezoneObj == dataPtr->literals[LIT_GMT] + || timezoneObj == dataPtr->gmtSetupTimeZoneUnnorm + ) { + if (dataPtr->gmtSetupTZData != NULL) { + return dataPtr->gmtSetupTZData; + } + out = &dataPtr->gmtSetupTZData; + } + else + if ( timezoneObj == dataPtr->prevSetupTimeZone + || timezoneObj == dataPtr->prevSetupTimeZoneUnnorm + ) { + if (dataPtr->prevSetupTZData != NULL) { + return dataPtr->prevSetupTZData; + } + out = &dataPtr->prevSetupTZData; + } + + ret = Tcl_ObjGetVar2(interp, dataPtr->literals[LIT_TZDATA], + timezoneObj, TCL_LEAVE_ERR_MSG); + + /* cache using corresponding slot and as last used */ + if (out != NULL) { + Tcl_SetObjRef(*out, ret); + } + else + if (dataPtr->lastSetupTimeZone != timezoneObj) { + SavePrevTimezoneObj(dataPtr); + Tcl_SetObjRef(dataPtr->lastSetupTimeZone, timezoneObj); + Tcl_UnsetObjRef(dataPtr->lastSetupTimeZoneUnnorm); + Tcl_SetObjRef(dataPtr->lastSetupTZData, ret); + } + return ret; +} + +/* + *---------------------------------------------------------------------- + * + * ClockGetSystemTimeZone -- + * + * Returns system (current) timezone. + * + * If system zone not yet cached, it executes ::tcl::clock::GetSystemTimeZone + * in given interpreter and caches its result. + * + * Results: + * Returns normalized timezone object. + * + *---------------------------------------------------------------------- + */ + +static Tcl_Obj * +ClockGetSystemTimeZone( + ClientData clientData, /* Opaque pointer to literal pool, etc. */ + Tcl_Interp *interp) /* Tcl interpreter */ +{ + ClockClientData *dataPtr = (ClockClientData *)clientData; + + /* if known (cached and same epoch) - return now */ + if (dataPtr->systemTimeZone != NULL + && dataPtr->lastTZEpoch == TzsetIfNecessary()) { + return dataPtr->systemTimeZone; + } + + Tcl_UnsetObjRef(dataPtr->systemTimeZone); + Tcl_UnsetObjRef(dataPtr->systemSetupTZData); + + if (Tcl_EvalObjv(interp, 1, &dataPtr->literals[LIT_GETSYSTEMTIMEZONE], 0) != TCL_OK) { + return NULL; + } + if (dataPtr->systemTimeZone == NULL) { + Tcl_SetObjRef(dataPtr->systemTimeZone, Tcl_GetObjResult(interp)); + } + Tcl_ResetResult(interp); + return dataPtr->systemTimeZone; +} + +/* + *---------------------------------------------------------------------- + * + * ClockSetupTimeZone -- + * + * Sets up the timezone. Loads tzdata, etc. + * + * Results: + * Returns normalized timezone object. + * + *---------------------------------------------------------------------- + */ + +Tcl_Obj * +ClockSetupTimeZone( + ClientData clientData, /* Opaque pointer to literal pool, etc. */ + Tcl_Interp *interp, /* Tcl interpreter */ + Tcl_Obj *timezoneObj) +{ + ClockClientData *dataPtr = (ClockClientData *)clientData; + int loaded; + Tcl_Obj *callargs[2]; + + /* if cached (if already setup this one) */ + if ( timezoneObj == dataPtr->literals[LIT_GMT] + && dataPtr->gmtSetupTZData != NULL + ) { + return timezoneObj; + } + if ( ( timezoneObj == dataPtr->lastSetupTimeZone + || timezoneObj == dataPtr->lastSetupTimeZoneUnnorm + ) && dataPtr->lastSetupTimeZone != NULL + ) { + return dataPtr->lastSetupTimeZone; + } + if ( ( timezoneObj == dataPtr->prevSetupTimeZone + || timezoneObj == dataPtr->prevSetupTimeZoneUnnorm + ) && dataPtr->prevSetupTimeZone != NULL + ) { + return dataPtr->prevSetupTimeZone; + } + + /* differentiate normalized (last, GMT and system) zones, because used often and already set */ + callargs[1] = NormTimezoneObj(dataPtr, timezoneObj, &loaded); + /* if loaded (setup already called for this TZ) */ + if (loaded) { + return callargs[1]; + } + + /* before setup just take a look in TZData variable */ + if (Tcl_ObjGetVar2(interp, dataPtr->literals[LIT_TZDATA], timezoneObj, 0)) { + /* put it to last slot and return normalized */ + TimezoneLoaded(dataPtr, callargs[1], timezoneObj); + return callargs[1]; + } + /* setup now */ + callargs[0] = dataPtr->literals[LIT_SETUPTIMEZONE]; + if (Tcl_EvalObjv(interp, 2, callargs, 0) == TCL_OK) { + /* save unnormalized last used */ + Tcl_SetObjRef(dataPtr->lastSetupTimeZoneUnnorm, timezoneObj); + return callargs[1]; + } + return NULL; +} + +/* + *---------------------------------------------------------------------- + * + * ClockFormatNumericTimeZone -- + * + * Formats a time zone as +hhmmss + * + * Parameters: + * z - Time zone in seconds east of Greenwich + * + * Results: + * Returns the time zone object (formatted in a numeric form) + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +Tcl_Obj * +ClockFormatNumericTimeZone(int z) { + char buf[12+1], *p; + + if ( z < 0 ) { + z = -z; + *buf = '-'; + } else { + *buf = '+'; + } + TclItoAw(buf+1, z / 3600, '0', 2); z %= 3600; + p = TclItoAw(buf+3, z / 60, '0', 2); z %= 60; + if (z != 0) { + p = TclItoAw(buf+5, z, '0', 2); + } + return Tcl_NewStringObj(buf, p - buf); } /* *---------------------------------------------------------------------- * @@ -309,15 +1398,15 @@ * * Tcl command that converts a UTC time to a local time by whatever means * 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: * Returns a standard Tcl result. * @@ -335,28 +1424,28 @@ Tcl_Interp *interp, /* Tcl interpreter */ int objc, /* Parameter count */ Tcl_Obj *const *objv) /* Parameter vector */ { ClockClientData *data = (ClockClientData *)clientData; - Tcl_Obj *const *lit = data->literals; Tcl_Obj *secondsObj; Tcl_Obj *dict; int changeover; TclDateFields fields; 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]; - if (Tcl_DictObjGet(interp, dict, lit[LIT_LOCALSECONDS], + if (Tcl_DictObjGet(interp, dict, data->literals[LIT_LOCALSECONDS], &secondsObj)!= TCL_OK) { return TCL_ERROR; } if (secondsObj == NULL) { Tcl_SetObjResult(interp, Tcl_NewStringObj("key \"localseconds\" not " @@ -364,11 +1453,11 @@ return TCL_ERROR; } 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; } /* * Copy-on-write; set the 'seconds' field in the dictionary and place the @@ -378,11 +1467,11 @@ if (Tcl_IsShared(dict)) { dict = Tcl_DuplicateObj(dict); created = 1; Tcl_IncrRefCount(dict); } - status = Tcl_DictObjPut(interp, dict, lit[LIT_SECONDS], + status = Tcl_DictObjPut(interp, dict, data->literals[LIT_SECONDS], Tcl_NewWideIntObj(fields.seconds)); if (status == TCL_OK) { Tcl_SetObjResult(interp, dict); } if (created) { @@ -398,16 +1487,15 @@ * * Tcl command that determines the values that [clock format] will use in * 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 * * Results: * Returns a dictonary populated with the fields: @@ -432,16 +1520,18 @@ Tcl_Obj *dict; ClockClientData *data = (ClockClientData *)clientData; 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 || TclGetIntFromObj(interp, objv[3], &changeover) != TCL_OK) { return TCL_ERROR; @@ -455,34 +1545,18 @@ if (objv[1]->typePtr == &tclBignumType) { Tcl_SetObjResult(interp, lit[LIT_INTEGER_VALUE_TOO_LARGE]); 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], Tcl_NewWideIntObj(fields.localSeconds)); Tcl_DictObjPut(NULL, dict, lit[LIT_SECONDS], @@ -517,10 +1591,62 @@ } /* *---------------------------------------------------------------------- * + * 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( + ClientData 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 * Day Number. * @@ -540,23 +1666,28 @@ static int FetchEraField( Tcl_Interp *interp, Tcl_Obj *dict, Tcl_Obj *key, - int *storePtr) + ERA_ENUM *storePtr) { Tcl_Obj *value = NULL; + int result, era; if (Tcl_DictObjGet(interp, dict, key, &value) != TCL_OK) { return TCL_ERROR; } if (value == NULL) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "expected key(s) not found in dictionary", -1)); return TCL_ERROR; } - return Tcl_GetIndexFromObj(interp, value, eras, "era", TCL_EXACT, storePtr); + result = Tcl_GetIndexFromObj(interp, value, eras, "era", TCL_EXACT, &era); + if (result == TCL_OK && storePtr) { + *storePtr = (ERA_ENUM)era; + } + return result; } static int FetchIntField( Tcl_Interp *interp, @@ -589,11 +1720,13 @@ ClockClientData *data = (ClockClientData *)clientData; Tcl_Obj *const *lit = data->literals; int changeover; int copied = 0; int status; - int era = 0; + ERA_ENUM era = CE; + + fields.tzName = NULL; /* * Check params. */ @@ -673,11 +1806,13 @@ ClockClientData *data = (ClockClientData *)clientData; Tcl_Obj *const *lit = data->literals; int changeover; int copied = 0; int status; - int era = 0; + ERA_ENUM era = CE; + + fields.tzName = NULL; /* * Check params. */ @@ -742,21 +1877,66 @@ *---------------------------------------------------------------------- */ static int ConvertLocalToUTC( + ClientData 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 */ int 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; } @@ -764,14 +1944,63 @@ * Special case: If the time zone is :localtime, the tzdata will be empty. * Use 'mktime' to convert the time to local */ if (rowc == 0) { - return ConvertLocalToUTCUsingC(interp, fields, changeover); + + if (ConvertLocalToUTCUsingC(interp, fields, changeover) != TCL_OK) { + return TCL_ERROR; + }; + + /* we cannot cache (ranges unknown yet) - todo: check later the DST-hole here */ + return TCL_OK; + } else { - return ConvertLocalToUTCUsingTable(interp, fields, rowc, rowv); + Tcl_WideInt rangesVal[2]; + + if (ConvertLocalToUTCUsingTable(interp, fields, rowc, rowv, + rangesVal) != TCL_OK) { + return TCL_ERROR; + }; + + seconds = fields->seconds; + + /* Cache the last conversion */ + if (ltzoc != NULL) { /* slot was found above */ + /* timezoneObj and changeover are the same */ + Tcl_SetObjRef(ltzoc->tzName, fields->tzName); /* may be NULL */ + } else { + /* no TZ in cache - just move second slot down and use the first one */ + ltzoc = &dataPtr->lastTZOffsCache[0]; + Tcl_UnsetObjRef(dataPtr->lastTZOffsCache[1].timezoneObj); + Tcl_UnsetObjRef(dataPtr->lastTZOffsCache[1].tzName); + memcpy(&dataPtr->lastTZOffsCache[1], ltzoc, sizeof(*ltzoc)); + Tcl_InitObjRef(ltzoc->timezoneObj, timezoneObj); + ltzoc->changeover = changeover; + Tcl_InitObjRef(ltzoc->tzName, fields->tzName); /* may be NULL */ + } + ltzoc->localSeconds = fields->localSeconds; + ltzoc->rangesVal[0] = rangesVal[0]; + ltzoc->rangesVal[1] = rangesVal[1]; + ltzoc->tzOffset = fields->tzOffset; } + + + /* check DST-hole: if retrieved seconds is out of range */ + if ( ltzoc->rangesVal[0] > seconds || seconds >= ltzoc->rangesVal[1] ) { + dstHole: + #if 0 + printf("given local-time is outside the time-zone (in DST-hole): " + "%d - offs %d => %d <= %d < %d\n", + (int)fields->localSeconds, fields->tzOffset, + (int)ltzoc->rangesVal[0], (int)seconds, (int)ltzoc->rangesVal[1]); + #endif + /* because we don't know real TZ (we're outsize), just invalidate local + * time (which could be verified in ClockValidDate later) */ + fields->localSeconds = TCL_INV_SECONDS; /* not valid seconds */ + } + return TCL_OK; } /* *---------------------------------------------------------------------- * @@ -793,19 +2022,22 @@ static int ConvertLocalToUTCUsingTable( Tcl_Interp *interp, /* Tcl interpreter */ TclDateFields *fields, /* Time to convert, with 'seconds' filled in */ int rowc, /* Number of points at which time changes */ - Tcl_Obj *const rowv[]) /* Points at which time changes */ + Tcl_Obj *const rowv[], /* Points at which time changes */ + Tcl_WideInt *rangesVal) /* Return bounds for time period */ { Tcl_Obj *row; int cellc; Tcl_Obj **cellv; - int have[8]; + struct { + Tcl_Obj *tzName; + int tzOffset; + } have[8]; int nHave = 0; int i; - int found; /* * Perform an initial lookup assuming that local == UTC, and locate the * last time conversion prior to that time. Get the offset from that row, * and look up again. Continue until we find an offset that we found @@ -813,39 +2045,40 @@ * don't enter an endless loop, as would otherwise happen when trying to * convert a non-existent time such as 02:30 during the US Spring Daylight * Saving Time transition. */ - 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 || TclGetIntFromObj(interp, cellv[1], &fields->tzOffset) != TCL_OK) { return TCL_ERROR; } - found = 0; - for (i = 0; !found && i < nHave; ++i) { - if (have[i] == fields->tzOffset) { - found = 1; - break; - } - } - if (!found) { - if (nHave == 8) { - Tcl_Panic("loop in ConvertLocalToUTCUsingTable"); - } - have[nHave++] = fields->tzOffset; - } - fields->seconds = fields->localSeconds - fields->tzOffset; - } - fields->tzOffset = have[i]; - fields->seconds = fields->localSeconds - fields->tzOffset; + for (i = 0; i < nHave; ++i) { + if (have[i].tzOffset == fields->tzOffset) { + goto found; + } + } + if (nHave == 8) { + Tcl_Panic("loop in ConvertLocalToUTCUsingTable"); + } + have[nHave].tzName = cellv[3]; + have[nHave++].tzOffset = fields->tzOffset; + fields->seconds = fields->localSeconds - fields->tzOffset; + } + + found: + fields->tzOffset = have[i].tzOffset; + fields->seconds = fields->localSeconds - fields->tzOffset; + Tcl_SetObjRef(fields->tzName, have[i].tzName); + return TCL_OK; } /* *---------------------------------------------------------------------- @@ -872,23 +2105,18 @@ int changeover) /* Julian Day of the Gregorian transition */ { 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); /* * Convert the date/time to a 'struct tm'. @@ -943,23 +2171,70 @@ * Populates the 'tzName' and 'tzOffset' fields. * *---------------------------------------------------------------------- */ -static int +int ConvertUTCToLocal( + ClientData 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 */ int rowc; /* Number of rows in tzdata */ Tcl_Obj **rowv; /* Pointers to the rows */ + ClockLastTZOffs * ltzoc = NULL; + + /* fast phase-out for shared GMT-object (don't need to convert UTC 2 UTC) */ + if (timezoneObj == dataPtr->literals[LIT_GMT]) { + fields->localSeconds = fields->seconds; + fields->tzOffset = 0; + if (dataPtr->gmtTZName == NULL) { + Tcl_Obj *tzName; + tzdata = ClockGetTZData(clientData, interp, timezoneObj); + if ( TclListObjGetElements(interp, tzdata, &rowc, &rowv) != TCL_OK + || Tcl_ListObjIndex(interp, rowv[0], 3, &tzName) != TCL_OK) { + return TCL_ERROR; + } + Tcl_SetObjRef(dataPtr->gmtTZName, tzName); + } + Tcl_SetObjRef(fields->tzName, dataPtr->gmtTZName); + return TCL_OK; + } + + /* + * Check cacheable conversion could be used + * (last-period UTC2Local cache within the same TZ and seconds) + */ + for (rowc = 0; rowc < 2; rowc++) { + ltzoc = &dataPtr->lastTZOffsCache[rowc]; + if (timezoneObj != ltzoc->timezoneObj || changeover != ltzoc->changeover) { + ltzoc = NULL; + continue; + } + if ( fields->seconds >= ltzoc->rangesVal[0] + && fields->seconds < ltzoc->rangesVal[1] + ) { + /* the same time zone and offset (UTC time inside the last minute) */ + fields->tzOffset = ltzoc->tzOffset; + fields->localSeconds = fields->seconds + fields->tzOffset; + Tcl_SetObjRef(fields->tzName, ltzoc->tzName); + return TCL_OK; + } + } /* * Unpack the tz data. */ + + tzdata = ClockGetTZData(clientData, interp, timezoneObj); + if (tzdata == NULL) { + return TCL_ERROR; + } if (TclListObjGetElements(interp, tzdata, &rowc, &rowv) != TCL_OK) { return TCL_ERROR; } @@ -967,14 +2242,51 @@ * Special case: If the time zone is :localtime, the tzdata will be empty. * Use 'localtime' to convert the time to local */ if (rowc == 0) { - return ConvertUTCToLocalUsingC(interp, fields, changeover); + + if (ConvertUTCToLocalUsingC(interp, fields, changeover) != TCL_OK) { + return TCL_ERROR; + } + + /* signal we need to revalidate TZ epoch next time fields gets used. */ + fields->flags |= CLF_CTZ; + + /* we cannot cache (ranges unknown yet) */ } else { - return ConvertUTCToLocalUsingTable(interp, fields, rowc, rowv); + Tcl_WideInt rangesVal[2]; + + if (ConvertUTCToLocalUsingTable(interp, fields, rowc, rowv, + rangesVal) != TCL_OK) { + return TCL_ERROR; + } + + /* converted using table (TZ isn't :localtime) */ + fields->flags &= ~CLF_CTZ; + + /* Cache the last conversion */ + if (ltzoc != NULL) { /* slot was found above */ + /* timezoneObj and changeover are the same */ + Tcl_SetObjRef(ltzoc->tzName, fields->tzName); + } else { + /* no TZ in cache - just move second slot down and use the first one */ + ltzoc = &dataPtr->lastTZOffsCache[0]; + Tcl_UnsetObjRef(dataPtr->lastTZOffsCache[1].timezoneObj); + Tcl_UnsetObjRef(dataPtr->lastTZOffsCache[1].tzName); + memcpy(&dataPtr->lastTZOffsCache[1], ltzoc, sizeof(*ltzoc)); + Tcl_InitObjRef(ltzoc->timezoneObj, timezoneObj); + ltzoc->changeover = changeover; + Tcl_InitObjRef(ltzoc->tzName, fields->tzName); + } + ltzoc->localSeconds = fields->localSeconds; + ltzoc->rangesVal[0] = rangesVal[0]; + ltzoc->rangesVal[1] = rangesVal[1]; + ltzoc->tzOffset = fields->tzOffset; } + + return TCL_OK; } /* *---------------------------------------------------------------------- * @@ -997,21 +2309,22 @@ ConvertUTCToLocalUsingTable( Tcl_Interp *interp, /* Tcl interpreter */ TclDateFields *fields, /* Fields of the date */ int rowc, /* Number of rows in the conversion table * (>= 1) */ - Tcl_Obj *const rowv[]) /* Rows of the conversion table */ + Tcl_Obj *const rowv[], /* Rows of the conversion table */ + Tcl_WideInt *rangesVal) /* Return bounds for time period */ { Tcl_Obj *row; /* Row containing the current information */ int cellc; /* Count of cells in the row (must be 4) */ Tcl_Obj **cellv; /* Pointers to the cells */ /* * Look up the nearest transition time. */ - row = LookupLastTransition(interp, fields->seconds, rowc, rowv); + 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) { return TCL_ERROR; } @@ -1018,12 +2331,11 @@ /* * Convert the time. */ - fields->tzName = cellv[3]; - Tcl_IncrRefCount(fields->tzName); + Tcl_SetObjRef(fields->tzName, cellv[3]); fields->localSeconds = fields->seconds + fields->tzOffset; return TCL_OK; } /* @@ -1052,30 +2364,30 @@ int changeover) /* Julian Day of the Gregorian transition */ { time_t tock; struct tm *timeVal; /* Time after conversion */ int diff; /* Time zone diff local-Greenwich */ - char buffer[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. */ tock = (time_t) fields->seconds; if ((Tcl_WideInt) tock != fields->seconds) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "number too large to represent as a Posix time", -1)); - Tcl_SetErrorCode(interp, "CLOCK", "argTooLarge", NULL); + Tcl_SetErrorCode(interp, "CLOCK", "argTooLarge", (char *)NULL); return TCL_ERROR; } TzsetIfNecessary(); timeVal = ThreadSafeLocalTime(&tock); if (timeVal == NULL) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "localtime failed (clock value may be too " "large/small to represent)", -1)); - Tcl_SetErrorCode(interp, "CLOCK", "localtimeFailed", NULL); + Tcl_SetErrorCode(interp, "CLOCK", "localtimeFailed", (char *)NULL); return TCL_ERROR; } /* * Fill in the date in 'fields' and use it to derive Julian Day. @@ -1105,19 +2417,16 @@ *buffer = '-'; diff = -diff; } 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); - } - fields->tzName = Tcl_NewStringObj(buffer, -1); - Tcl_IncrRefCount(fields->tzName); + 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); + } + Tcl_SetObjRef(fields->tzName, Tcl_NewStringObj(buffer, p - buffer)); return TCL_OK; } /* *---------------------------------------------------------------------- @@ -1131,21 +2440,22 @@ * Returns a pointer to the row, or NULL if an error occurs. * *---------------------------------------------------------------------- */ -static Tcl_Obj * +Tcl_Obj * LookupLastTransition( Tcl_Interp *interp, /* Interpreter for error messages */ Tcl_WideInt tick, /* Time from the epoch */ int rowc, /* Number of rows of tzdata */ - Tcl_Obj *const *rowv) /* Rows in tzdata */ + Tcl_Obj *const *rowv, /* Rows in tzdata */ + Tcl_WideInt *rangesVal) /* Return bounds for time period */ { - int l; + int l = 0; int 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. */ @@ -1157,19 +2467,22 @@ /* * Bizarre case - first row doesn't begin at MIN_WIDE_INT. Return it * anyway. */ - if (tick < compVal) { + if (tick < (fromVal = compVal)) { + if (rangesVal) { + rangesVal[0] = fromVal; + rangesVal[1] = toVal; + } return rowv[0]; } /* * Binary-search to find the transition. */ - l = 0; u = rowc-1; while (l < u) { int m = (l + u + 1) / 2; if (Tcl_ListObjIndex(interp, rowv[m], 0, &compObj) != TCL_OK || @@ -1176,14 +2489,21 @@ TclGetWideIntFromObj(interp, compObj, &compVal) != TCL_OK) { return NULL; } if (tick >= compVal) { l = m; + fromVal = compVal; } else { u = m-1; + toVal = compVal; } } + + if (rangesVal) { + rangesVal[0] = fromVal; + rangesVal[1] = toVal; + } return rowv[l]; } /* *---------------------------------------------------------------------- @@ -1209,10 +2529,12 @@ int changeover) /* Julian Day Number of the Gregorian * transition */ { 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. */ @@ -1273,14 +2595,14 @@ static void GetGregorianEraYearDay( TclDateFields *fields, /* Date fields containing 'julianDay' */ int changeover) /* Gregorian transition date */ { - int jday = fields->julianDay; - int day; - int year; - int n; + Tcl_WideInt jday = fields->julianDay; + Tcl_WideInt day; + Tcl_WideInt year; + Tcl_WideInt n; if (jday >= changeover) { /* * Gregorian calendar. */ @@ -1390,15 +2712,31 @@ GetMonthDay( TclDateFields *fields) /* Date to convert */ { int day = fields->dayOfYear; int month; - const int *h = hath[IsGregorianLeapYear(fields)]; + const int *dipm = daysInPriorMonths[IsGregorianLeapYear(fields)]; - for (month = 0; month < 12 && day > h[month]; ++month) { - day -= h[month]; + /* + * Estimate month by calculating `dayOfYear / (365/12)` + */ + month = (day*12) / dipm[12]; + /* then do forwards backwards correction */ + while (1) { + if (day > dipm[month]) { + if (month >= 11 || day <= dipm[month+1]) { + break; + } + month++; + } else { + if (month == 0) { + break; + } + month--; + } } + day -= dipm[month]; fields->month = month+1; fields->dayOfMonth = day; } /* @@ -1416,19 +2754,21 @@ * Stores 'julianDay' in the fields. * *---------------------------------------------------------------------- */ -static void +void GetJulianDayFromEraYearWeekDay( TclDateFields *fields, /* Date to convert */ int changeover) /* Julian Day Number of the Gregorian * transition */ { - int firstMonday; /* Julian day number of week 1, day 1 in the + Tcl_WideInt firstMonday; /* Julian day number of week 1, day 1 in the * given year */ TclDateFields firstWeek; + + firstWeek.tzName = NULL; /* * Find January 4 in the ISO8601 year, which will always be in week 1. */ @@ -1467,16 +2807,17 @@ * Stores day number in 'julianDay' * *---------------------------------------------------------------------- */ -static void +void GetJulianDayFromEraYearMonthDay( TclDateFields *fields, /* Date to convert */ int changeover) /* Gregorian transition date as a Julian Day */ { - int year, ym1, month, mm1, q, r, ym1o4, ym1o100, ym1o400; + Tcl_WideInt year, ym1, ym1o4, ym1o100, ym1o400; + int month, mm1, q, r; if (fields->era == BCE) { year = 1 - fields->year; } else { year = fields->year; @@ -1563,10 +2904,65 @@ } /* *---------------------------------------------------------------------- * + * 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->era == BCE) { + 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 * Gregorian calendar. * @@ -1574,15 +2970,15 @@ * Returns 1 for a leap year, 0 otherwise. * *---------------------------------------------------------------------- */ -static int +int IsGregorianLeapYear( TclDateFields *fields) /* Date to test */ { - int year = fields->year; + Tcl_WideInt year = fields->year; if (fields->era == BCE) { year = 1 - year; } if (year%4 != 0) { @@ -1610,14 +3006,14 @@ * Returns the Julian Day Number * *---------------------------------------------------------------------- */ -static int +static Tcl_WideInt WeekdayOnOrBefore( int dayOfWeek, /* Day of week; Sunday == 0 or 7 */ - int julianDay) /* Reference date */ + Tcl_WideInt julianDay) /* Reference date */ { int k = (dayOfWeek + 6) % 7; if (k < 0) { k += 7; } @@ -1668,22 +3064,20 @@ } #ifdef _WIN32 varName = (const WCHAR *)Tcl_WinUtfToTChar(TclGetString(objv[1]), -1, &ds); varValue = _wgetenv(varName); Tcl_DStringFree(&ds); - if (varValue == NULL) { - varValue = L""; + if (varValue != NULL) { + Tcl_WinTCharToUtf((TCHAR *)varValue, -1, &ds); + Tcl_DStringResult(interp, &ds); } - Tcl_WinTCharToUtf((TCHAR *)varValue, -1, &ds); - Tcl_DStringResult(interp, &ds); #else varName = TclGetString(objv[1]); varValue = getenv(varName); - if (varValue == NULL) { - varValue = ""; + if (varValue != NULL) { + Tcl_SetObjResult(interp, Tcl_NewStringObj(varValue, -1)); } - Tcl_SetObjResult(interp, Tcl_NewStringObj(varValue, -1)); #endif return TCL_OK; } /* @@ -1871,126 +3265,1275 @@ } Tcl_SetObjResult(interp, Tcl_NewWideIntObj(TclpGetMicroseconds())); return TCL_OK; } -/* - *----------------------------------------------------------------------------- - * - * ClockParseformatargsObjCmd -- - * - * Parses the arguments for [clock format]. - * - * Results: - * Returns a standard Tcl result, whose value is a four-element list - * comprising the time format, the locale, and the timezone. - * - * 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. - * - *----------------------------------------------------------------------------- - */ - -static int -ClockParseformatargsObjCmd( - ClientData 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; - 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] - int gmtFlag = 0; - static const char *const options[] = { /* Command line options expected */ - "-format", "-gmt", "-locale", - "-timezone", NULL }; - enum optionInd { - CLOCK_FORMAT_FORMAT, CLOCK_FORMAT_GMT, CLOCK_FORMAT_LOCALE, - CLOCK_FORMAT_TIMEZONE - }; - 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; - - /* - * 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", NULL); - return TCL_ERROR; +static inline void +ClockInitFmtScnArgs( + ClientData clientData, + Tcl_Interp *interp, + ClockFmtScnCmdArgs *opts) +{ + memset(opts, 0, sizeof(*opts)); + opts->clientData = clientData; + opts->interp = interp; +} + +/* + *----------------------------------------------------------------------------- + * + * ClockParseFmtScnArgs -- + * + * Parses the arguments for sub-commands "scan", "format" and "add". + * + * 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. + * + * 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 +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 */ + 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[] = { + "-base", "-format", "-gmt", "-locale", "-timezone", "-validate", NULL + }; + enum optionInd { + 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. */ + int i; + Tcl_WideInt baseVal; /* Base time, expressed in seconds from the Epoch */ + + 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", - Tcl_GetString(objv[i]), 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]); - Tcl_SetErrorCode(interp, "CLOCK", "gmtWithTimezone", NULL); + if ((saw & (1 << CLC_ARGS_GMT)) + && (saw & (1 << CLC_ARGS_TIMEZONE))) { + Tcl_SetObjResult(interp, Tcl_NewStringObj("cannot use -gmt and -timezone in same call", -1)); + Tcl_SetErrorCode(interp, "CLOCK", "gmtWithTimezone", (char *)NULL); return TCL_ERROR; } if (gmtFlag) { - timezoneObj = litPtr[LIT_GMT]; + opts->timezoneObj = dataPtr->literals[LIT_GMT]; + } + else + /* If time zone not specified use system time zone */ + if ( opts->timezoneObj == NULL + || TclGetString(opts->timezoneObj) == NULL + || opts->timezoneObj->length == 0 + ) { + opts->timezoneObj = ClockGetSystemTimeZone(opts->clientData, interp); + if (opts->timezoneObj == NULL) { + return TCL_ERROR; + } + } + + /* Setup timezone (normalize object if needed and load TZ on demand) */ + + opts->timezoneObj = ClockSetupTimeZone(opts->clientData, interp, opts->timezoneObj); + if (opts->timezoneObj == NULL) { + return TCL_ERROR; + } + + /* Base (by scan or add) or clock value (by format) */ + + if (opts->baseObj != NULL) { + Tcl_Obj *baseObj = opts->baseObj; + /* bypass integer recognition if looks like option "-now" */ + if ( + (baseObj->length == 4 && baseObj->bytes && *(baseObj->bytes+1) == 'n') || + TclGetWideIntFromObj(NULL, baseObj, &baseVal) != TCL_OK + ) { + + /* we accept "-now" as current date-time */ + static const char *const nowOpts[] = { + "-now", NULL + }; + int idx; + if (Tcl_GetIndexFromObj(NULL, baseObj, nowOpts, "seconds or -now", + TCL_EXACT, &idx) == TCL_OK + ) { + goto baseNow; + } + + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "expected integer but got \"%s\"", + Tcl_GetString(baseObj))); + Tcl_SetErrorCode(interp, "TCL", "VALUE", "INTEGER", (char *)NULL); + i = 1; + goto badOption; + } + /* + * Seconds could be an unsigned number that overflowed. Make sure + * that it isn't. Additionally it may be too complex to calculate + * julianday etc (forwards/backwards) by too large/small values, thus + * just let accept a bit shorter values to avoid overflow. + * Note the year is currently an integer, thus avoid to overflow it also. + */ + + if ( baseObj->typePtr == &tclBignumType + || baseVal < TCL_MIN_SECONDS || baseVal > TCL_MAX_SECONDS + ) { + Tcl_SetObjResult(interp, dataPtr->literals[LIT_INTEGER_VALUE_TOO_LARGE]); + return TCL_ERROR; + } + + } else { + +baseNow: + { + Tcl_Time now; + Tcl_GetTime(&now); + baseVal = (Tcl_WideInt) now.sec; + } + } + + /* + * Extract year, month and day from the base time for the parser to use as + * defaults + */ + + /* check base fields already cached (by TZ, last-second cache) */ + if ( dataPtr->lastBase.timezoneObj == opts->timezoneObj + && dataPtr->lastBase.date.seconds == baseVal + && (!(dataPtr->lastBase.date.flags & CLF_CTZ) + || dataPtr->lastTZEpoch == TzsetIfNecessary()) + ) { + memcpy(date, &dataPtr->lastBase.date, ClockCacheableDateFieldsSize); + } else { + /* extact fields from base */ + date->seconds = baseVal; + if (ClockGetDateFields(opts->clientData, interp, date, opts->timezoneObj, + GREGORIAN_CHANGE_DATE) != TCL_OK) { /* TODO - GREGORIAN_CHANGE_DATE should be locale-dependent */ + return TCL_ERROR; + } + /* cache last base */ + memcpy(&dataPtr->lastBase.date, date, ClockCacheableDateFieldsSize); + Tcl_SetObjRef(dataPtr->lastBase.timezoneObj, opts->timezoneObj); + } + + return TCL_OK; + +badOptionMsg: + + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "bad option \"%s\": must be %s", + TclGetString(objv[i]), syntax) + ); + +badOption: + + Tcl_SetErrorCode(interp, "CLOCK", "badOption", + (i < objc) ? Tcl_GetString(objv[i]) : (char *)NULL, (char *)NULL); + + return TCL_ERROR; +} + +/*---------------------------------------------------------------------- + * + * ClockFormatObjCmd -- , clock format -- + * + * This function is invoked to process the Tcl "clock format" command. + * + * Formats a count of seconds since the Posix Epoch as a time of day. + * + * The 'clock format' command formats times of day for output. Refer + * to the user documentation to see what it does. + * + * Results: + * Returns a standard Tcl result. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +int +ClockFormatObjCmd( + ClientData 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 = "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, 1, objv, syntax); + Tcl_SetErrorCode(interp, "CLOCK", "wrongNumArgs", (char *)NULL); + return TCL_ERROR; + } + + memset(&dateFmt, 0, sizeof(dateFmt)); + + /* + * Extract values for the keywords. + */ + + ClockInitFmtScnArgs(clientData, interp, &opts); + ret = ClockParseFmtScnArgs(&opts, &dateFmt.date, objc, objv, + CLC_FMT_ARGS, "-format, -gmt, -locale, or -timezone"); + if (ret != TCL_OK) { + goto done; + } + + /* Default format */ + if (opts.formatObj == NULL) { + opts.formatObj = dataPtr->literals[LIT__DEFAULT_FORMAT]; + } + + /* Use compiled version of Format - */ + + ret = ClockFormat(&dateFmt, &opts); + +done: + + Tcl_UnsetObjRef(dateFmt.date.tzName); + + if (ret != TCL_OK) { + return ret; + } + + return TCL_OK; +} + +/*---------------------------------------------------------------------- + * + * ClockScanObjCmd -- , clock scan -- + * + * This function is invoked to process the Tcl "clock scan" command. + * + * Inputs a count of seconds since the Posix Epoch as a time of day. + * + * The 'clock scan' command scans times of day on input. Refer to the + * user documentation to see what it does. + * + * Results: + * Returns a standard Tcl result. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +int +ClockScanObjCmd( + ClientData 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 = "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, 1, 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; + } + + /* Convert date info structure into UTC seconds */ + + ret = ClockScanCommit(&yy, &opts); + if (ret != TCL_OK) { + goto done; + } + + /* Apply validation rules, if expected */ + if ( (opts.flags & CLF_VALIDATE) ) { + ret = ClockValidDate(&yy, &opts, opts.formatObj == NULL ? 2 : 3); + if (ret != TCL_OK) { + goto done; + } + } + +done: + + Tcl_UnsetObjRef(yy.date.tzName); + + if (ret != TCL_OK) { + return ret; + } + + Tcl_SetObjResult(interp, Tcl_NewWideIntObj(yy.date.seconds)); + return TCL_OK; +} + +/*---------------------------------------------------------------------- + * + * ClockScanCommit -- + * + * Converts date info structure into UTC seconds. + * + * Results: + * Returns a standard Tcl result. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static int +ClockScanCommit( + DateInfo *info, /* Clock scan info structure */ + ClockFmtScnCmdArgs *opts) /* Format, locale, timezone and base */ +{ + /* If needed assemble julianDay using year, month, etc. */ + if (info->flags & CLF_ASSEMBLE_JULIANDAY) { + if ((info->flags & CLF_ISO8601WEAK)) { + GetJulianDayFromEraYearWeekDay(&yydate, GREGORIAN_CHANGE_DATE); + } + else + if ( !(info->flags & CLF_DAYOFYEAR) /* no day of year */ + || (info->flags & (CLF_DAYOFMONTH|CLF_MONTH)) /* yymmdd over yyddd */ + == (CLF_DAYOFMONTH|CLF_MONTH) + ) { + GetJulianDayFromEraYearMonthDay(&yydate, GREGORIAN_CHANGE_DATE); + } else { + GetJulianDayFromEraYearDay(&yydate, GREGORIAN_CHANGE_DATE); + } + info->flags |= CLF_ASSEMBLE_SECONDS; + info->flags &= ~CLF_ASSEMBLE_JULIANDAY; + } + + /* some overflow checks */ + if (info->flags & CLF_JULIANDAY) { + ClockClientData *dataPtr = (ClockClientData *)opts->clientData; + double curJDN = (double)yydate.julianDay + + ((double)yySecondOfDay - SECONDS_PER_DAY/2) / SECONDS_PER_DAY; + if (curJDN > dataPtr->maxJDN) { + Tcl_SetObjResult(opts->interp, Tcl_NewStringObj( + "requested date too large to represent", -1)); + Tcl_SetErrorCode(opts->interp, "CLOCK", "dateTooLarge", (char *)NULL); + return TCL_ERROR; + } + } + + /* Local seconds to UTC (stored in yydate.seconds) */ + + if (info->flags & (CLF_ASSEMBLE_SECONDS)) { + yydate.localSeconds = + -210866803200L + + ( SECONDS_PER_DAY * (Tcl_WideInt)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 & 1)) { + goto stage_2; + } + + /* 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 & 2)) { + return TCL_OK; + } + + /* + * 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 = Tcl_NewObj(); + 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.era = CE; + info->flags |= CLF_ASSEMBLE_JULIANDAY|CLF_ASSEMBLE_SECONDS; + } + + /* + * If the caller supplied a time zone in the string, make it into a time + * zone indicator of +-hhmm and setup this time zone. + */ + + if (info->flags & CLF_ZONE) { + if (yyTimezone || !yyDSTmode) { + /* Real time zone from numeric zone */ + Tcl_Obj *tzObjStor = NULL; + int minEast = -yyTimezone; + int dstFlag = 1 - yyDSTmode; + tzObjStor = ClockFormatNumericTimeZone( + 60 * minEast + 3600 * dstFlag); + Tcl_IncrRefCount(tzObjStor); + + opts->timezoneObj = ClockSetupTimeZone(dataPtr, interp, tzObjStor); + + Tcl_DecrRefCount(tzObjStor); + } else { + /* simplest case - GMT / UTC */ + opts->timezoneObj = ClockSetupTimeZone(dataPtr, interp, + dataPtr->literals[LIT_GMT]); + } + if (opts->timezoneObj == NULL) { + goto done; + } + + // Tcl_SetObjRef(yydate.tzName, opts->timezoneObj); + + info->flags |= CLF_ASSEMBLE_SECONDS; + } + + /* + * For freescan apply validation rules (stage 1) before mixed with + * relative time (otherwise always valid recalculated date & time). + */ + if ( (opts->flags & CLF_VALIDATE) ) { + if (ClockValidDate(info, opts, 1) != 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; + } + + /* + * Do relative (ordinal) month + */ + + 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.era = CE; + 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; +} + + +/*---------------------------------------------------------------------- + * + * 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( + ClientData 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 = "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, 1, 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, syntax); + 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; + } } /* - * Return options as a list. + * Do relative times (if not yet already processed interim): */ - Tcl_SetObjResult(interp, Tcl_NewListObj(3, results)); - return TCL_OK; + if (info->flags & CLF_RELCONV) { + if (ClockCalcRelTime(info) != TCL_OK) { + goto done; + } + } + + /* Convert date info structure into UTC seconds */ + + ret = ClockScanCommit(&yy, &opts); + +done: + + Tcl_UnsetObjRef(yy.date.tzName); + + if (ret != TCL_OK) { + return ret; + } -#undef timezoneObj -#undef localeObj -#undef formatObj + Tcl_SetObjResult(interp, Tcl_NewWideIntObj(yy.date.seconds)); + return TCL_OK; } /*---------------------------------------------------------------------- * * ClockSecondsObjCmd - @@ -2029,10 +4572,81 @@ } /* *---------------------------------------------------------------------- * + * ClockSafeCatchCmd -- + * + * Same as "::catch" command but avoids overwriting of interp state. + * + * See [554117edde] for more info (and proper solution). + * + *---------------------------------------------------------------------- + */ +int +ClockSafeCatchCmd( + ClientData dummy, + 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; + (void)dummy; + + if (objc == 1) { + /* wrong # args : */ + return Tcl_CatchObjCmd(NULL, interp, objc, objv); + } + + statePtr = (InterpState *)Tcl_SaveInterpState(interp, 0); + if (!statePtr->errorInfo) { + /* todo: avoid traced get of errorInfo here */ + Tcl_InitObjRef(statePtr->errorInfo, + Tcl_ObjGetVar2(interp, iPtr->eiVar, NULL, 0)); + flags |= ERR_LEGACY_COPY; + } + if (!statePtr->errorCode) { + /* todo: avoid traced get of errorCode here */ + Tcl_InitObjRef(statePtr->errorCode, + Tcl_ObjGetVar2(interp, iPtr->ecVar, NULL, 0)); + flags |= ERR_LEGACY_COPY; + } + + /* original catch */ + ret = Tcl_CatchObjCmd(NULL, interp, objc, objv); + + if (ret == TCL_ERROR) { + Tcl_DiscardInterpState((Tcl_InterpState)statePtr); + return TCL_ERROR; + } + /* overwrite result in state with catch result */ + Tcl_SetObjRef(statePtr->objResult, Tcl_GetObjResult(interp)); + /* set result (together with restore state) to interpreter */ + (void) Tcl_RestoreInterpState(interp, (Tcl_InterpState)statePtr); + /* todo: unless ERR_LEGACY_COPY not set in restore (branch [bug-554117edde] not merged yet) */ + iPtr->flags |= (flags & ERR_LEGACY_COPY); + return ret; +} + +/* + *---------------------------------------------------------------------- + * * TzsetIfNecessary -- * * Calls the tzset() library function if the contents of the TZ * environment variable has changed. * @@ -2052,84 +4666,63 @@ #define wcslen strlen #define wcscmp strcmp #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 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 */ /* * Prevent performance regression on some platforms by resolving of system time zone: * small latency for check whether environment was changed (once per second) - * no latency if environment was changed with tcl-env (compare both epoch values) + * no latency if environment was chaned with tcl-env (compare both epoch values) */ 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(); if (tzWas != NULL && tzWas != (WCHAR *)INT2PTR(-1)) { ckfree(tzWas); } tzWas = (WCHAR *)ckalloc(sizeof(WCHAR) * (wcslen(tzIsNow) + 1)); wcscpy(tzWas, tzIsNow); + tzWasEpoch++; } else if (tzIsNow == NULL && tzWas != NULL) { tzset(); if (tzWas != (WCHAR *)INT2PTR(-1)) ckfree(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( - ClientData 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]); - } - ckfree(data->literals); - ckfree(data); - } + + return tzWasEpoch; } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ ADDED generic/tclClockFmt.c Index: generic/tclClockFmt.c ================================================================== --- /dev/null +++ generic/tclClockFmt.c @@ -0,0 +1,3401 @@ +/* + * 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(ClientData 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 int +_str2int( + int *out, + const char *p, + const char *e, + int sign) +{ + int val = 0, prev = 0; + if (sign >= 0) { + while (p < e) { + val = val * 10 + (*p++ - '0'); + if (val < prev) { + return TCL_ERROR; + } + prev = val; + } + } else { + while (p < e) { + val = val * 10 - (*p++ - '0'); + if (val > prev) { + return TCL_ERROR; + } + prev = val; + } + } + *out = val; + return TCL_OK; +} + +static inline int +_str2wideInt( + Tcl_WideInt *out, + const char *p, + const char *e, + int sign) +{ + Tcl_WideInt val = 0, prev = 0; + if (sign >= 0) { + while (p < e) { + val = val * 10 + (*p++ - '0'); + if (val < prev) { + return TCL_ERROR; + } + prev = val; + } + } else { + while (p < e) { + val = val * 10 - (*p++ - '0'); + if (val > prev) { + return TCL_ERROR; + } + prev = val; + } + } + *out = val; + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * _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 >= 10000000000L) { + Tcl_WideInt val2; + val2 = val / 10000000000L; + 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 <= -10000000000L) { + Tcl_WideInt val2; + val2 = val / 10000000000L; + 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_HashTable *tablePtr, /* 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); + (void)tablePtr; + + allocsize += (size = strlen(string) + 1); + if (size > sizeof(hPtr->key)) { + allocsize -= sizeof(hPtr->key); + } + + fss = (ClockFmtScnStorage *)ckalloc(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) { + ckfree(fss->scnTok); + fss->scnTok = NULL; + fss->scnTokC = 0; + } + if (fss->fmtTok != NULL) { + ckfree(fss->fmtTok); + fss->fmtTok = NULL; + fss->fmtTokC = 0; + } + + ckfree(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 "ckfree" 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 */ +}; + +#define ObjClockFmtScn(objPtr) \ + (*((ClockFmtScnStorage **)&(objPtr)->internalRep.twoPtrValue.ptr1)) + +#define ObjLocFmtKey(objPtr) \ + (*((Tcl_Obj **)&(objPtr)->internalRep.twoPtrValue.ptr2)) + +static void +ClockFmtObj_DupInternalRep( + Tcl_Obj *srcPtr, + Tcl_Obj *copyPtr) +{ + ClockFmtScnStorage *fss = ObjClockFmtScn(srcPtr); + + if (fss != NULL) { + Tcl_MutexLock(&ClockFmtMutex); + fss->objRefCount++; + Tcl_MutexUnlock(&ClockFmtMutex); + } + + ObjClockFmtScn(copyPtr) = fss; + /* regards special case - format not localizable */ + if (ObjLocFmtKey(srcPtr) != srcPtr) { + Tcl_InitObjRef(ObjLocFmtKey(copyPtr), ObjLocFmtKey(srcPtr)); + } else { + ObjLocFmtKey(copyPtr) = copyPtr; + } + copyPtr->typePtr = &ClockFmtObjType; + + + /* if no format representation, dup string representation */ + if (fss == NULL) { + copyPtr->bytes = (char *)ckalloc(srcPtr->length + 1); + memcpy(copyPtr->bytes, srcPtr->bytes, srcPtr->length + 1); + copyPtr->length = srcPtr->length; + } +} + +static void +ClockFmtObj_FreeInternalRep( + Tcl_Obj *objPtr) +{ + ClockFmtScnStorage *fss = ObjClockFmtScn(objPtr); + if (fss != NULL) { + Tcl_MutexLock(&ClockFmtMutex); + /* decrement object reference count of format/scan storage */ + if (--fss->objRefCount <= 0) { + #if CLOCK_FMT_SCN_STORAGE_GC_SIZE > 0 + /* don't remove it right now (may be reusable), just add to GC */ + ClockFmtScnStorageGC_In(fss); + #else + /* remove storage (format representation) */ + ClockFmtScnStorageDelete(fss); + #endif + } + Tcl_MutexUnlock(&ClockFmtMutex); + } + ObjClockFmtScn(objPtr) = NULL; + if (ObjLocFmtKey(objPtr) != objPtr) { + Tcl_UnsetObjRef(ObjLocFmtKey(objPtr)); + } else { + ObjLocFmtKey(objPtr) = NULL; + } + objPtr->typePtr = NULL; +}; + +static int +ClockFmtObj_SetFromAny( + Tcl_Interp *interp, + Tcl_Obj *objPtr) +{ + /* validate string representation before free old internal representation */ + (void)interp; + (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"; + int 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 *)ckalloc((size_t)++len); + if (objPtr->bytes) + memcpy(objPtr->bytes, name, len); +} + +/* + *---------------------------------------------------------------------- + * + * ClockFrmObjGetLocFmtKey -- + * + * Retrieves format key object used to search localized format. + * + * This is normally stored in second pointer of internal representation. + * If format object is not localizable, it is equal the given format + * pointer (special case to fast fallback by not-localizable formats). + * + * Results: + * Returns tcl object with key or format object if not localizable. + * + * Side effects: + * Converts given format object to ClockFmtObjType on demand for caching + * the key inside its internal representation. + * + *---------------------------------------------------------------------- + */ + +Tcl_Obj* +ClockFrmObjGetLocFmtKey( + Tcl_Interp *interp, + Tcl_Obj *objPtr) +{ + Tcl_Obj *keyObj; + + if (objPtr->typePtr != &ClockFmtObjType) { + if (ClockFmtObj_SetFromAny(interp, objPtr) != TCL_OK) { + return NULL; + } + } + + keyObj = ObjLocFmtKey(objPtr); + if (keyObj) { + return keyObj; + } + + keyObj = Tcl_ObjPrintf("FMT_%s", TclGetString(objPtr)); + Tcl_InitObjRef(ObjLocFmtKey(objPtr), keyObj); + + return keyObj; +} + +/* + *---------------------------------------------------------------------- + * + * FindOrCreateFmtScnStorage -- + * + * Retrieves format storage for given string format. + * + * This will find the given format in the global storage hash table + * or create a format storage object on demaind and save the + * reference in the first pointer of internal representation of given + * object. + * + * Results: + * Returns scan/format storage pointer to ClockFmtScnStorage. + * + * Side effects: + * Converts given format object to ClockFmtObjType on demand for caching + * the format storage reference inside its internal representation. + * Increments objRefCount of the ClockFmtScnStorage reference. + * + *---------------------------------------------------------------------- + */ + +static ClockFmtScnStorage * +FindOrCreateFmtScnStorage( + Tcl_Interp *interp, + Tcl_Obj *objPtr) +{ + const char *strFmt = TclGetString(objPtr); + ClockFmtScnStorage *fss = NULL; + int isNew; + Tcl_HashEntry *hPtr; + + Tcl_MutexLock(&ClockFmtMutex); + + /* if not yet initialized */ + if (!initialized) { + /* initialize type */ + memcpy(&ClockFmtScnStorageHashKeyType, &tclStringHashKeyType, sizeof(tclStringHashKeyType)); + ClockFmtScnStorageHashKeyType.allocEntryProc = ClockFmtScnStorageAllocProc; + ClockFmtScnStorageHashKeyType.freeEntryProc = ClockFmtScnStorageFreeProc; + + /* initialize hash table */ + Tcl_InitCustomHashTable(&FmtScnHashTable, TCL_CUSTOM_TYPE_KEYS, + &ClockFmtScnStorageHashKeyType); + + initialized = 1; + Tcl_CreateExitHandler(ClockFrmScnFinalize, NULL); + } + + /* get or create entry (and alocate storage) */ + hPtr = Tcl_CreateHashEntry(&FmtScnHashTable, strFmt, &isNew); + if (hPtr != NULL) { + + fss = FmtScn4HashEntry(hPtr); + + #if CLOCK_FMT_SCN_STORAGE_GC_SIZE > 0 + /* unlink if it is currently in GC */ + if (isNew == 0 && fss->objRefCount == 0) { + ClockFmtScnStorage_GC_Out(fss); + } + #endif + + /* new reference, so increment in lock right now */ + fss->objRefCount++; + + ObjClockFmtScn(objPtr) = fss; + } + + Tcl_MutexUnlock(&ClockFmtMutex); + + if (fss == NULL && interp != NULL) { + Tcl_AppendResult(interp, "retrieve clock format failed \"", + strFmt ? strFmt : "", "\"", NULL); + Tcl_SetErrorCode(interp, "TCL", "EINVAL", (char *)NULL); + } + + return fss; +} + +/* + *---------------------------------------------------------------------- + * + * Tcl_GetClockFrmScnFromObj -- + * + * Returns a clock format/scan representation of (*objPtr), if possible. + * If something goes wrong, NULL is returned, and if interp is non-NULL, + * an error message is written there. + * + * Results: + * Valid representation of type ClockFmtScnStorage. + * + * Side effects: + * Caches the ClockFmtScnStorage reference as the internal rep of (*objPtr) + * and in global hash table, shared across all threads. + * + *---------------------------------------------------------------------- + */ + +ClockFmtScnStorage * +Tcl_GetClockFrmScnFromObj( + Tcl_Interp *interp, + Tcl_Obj *objPtr) +{ + ClockFmtScnStorage *fss; + + if (objPtr->typePtr != &ClockFmtObjType) { + if (ClockFmtObj_SetFromAny(interp, objPtr) != TCL_OK) { + return NULL; + } + } + + fss = ObjClockFmtScn(objPtr); + + if (fss == NULL) { + fss = FindOrCreateFmtScnStorage(interp, objPtr); + } + + return fss; +} +/* + *---------------------------------------------------------------------- + * + * ClockLocalizeFormat -- + * + * Wrap the format object in options to the localized format, + * corresponding given locale. + * + * This searches localized format in locale catalog, and if not yet + * exists, it executes ::tcl::clock::LocalizeFormat in given interpreter + * and caches its result in the locale catalog. + * + * Results: + * Localized format object. + * + * Side effects: + * Caches the localized format inside locale catalog. + * + *---------------------------------------------------------------------- + */ + +Tcl_Obj * +ClockLocalizeFormat( + ClockFmtScnCmdArgs *opts) +{ + ClockClientData *dataPtr = (ClockClientData *)opts->clientData; + Tcl_Obj *valObj = NULL, *keyObj; + + keyObj = ClockFrmObjGetLocFmtKey(opts->interp, opts->formatObj); + + /* special case - format object is not localizable */ + if (keyObj == opts->formatObj) { + return opts->formatObj; + } + + /* prevents loss of key object if the format object (where key stored) + * becomes changed (loses its internal representation during evals) */ + Tcl_IncrRefCount(keyObj); + + if (opts->mcDictObj == NULL) { + ClockMCDict(opts); + if (opts->mcDictObj == NULL) + goto done; + } + + /* try to find in cache within locale mc-catalog */ + if (Tcl_DictObjGet(NULL, opts->mcDictObj, + keyObj, &valObj) != TCL_OK) { + goto done; + } + + /* call LocalizeFormat locale format fmtkey */ + if (valObj == NULL) { + Tcl_Obj *callargs[4]; + callargs[0] = dataPtr->literals[LIT_LOCALIZE_FORMAT]; + callargs[1] = opts->localeObj; + callargs[2] = opts->formatObj; + callargs[3] = opts->mcDictObj; + if (Tcl_EvalObjv(opts->interp, 4, callargs, 0) == TCL_OK + ) { + valObj = Tcl_GetObjResult(opts->interp); + } + + /* ensure mcDictObj remains unshared */ + if (opts->mcDictObj->refCount > 1) { + /* smart reference (shared dict as object with no ref-counter) */ + opts->mcDictObj = TclDictObjSmartRef(opts->interp, + opts->mcDictObj); + } + if (!valObj) { + goto done; + } + /* cache it inside mc-dictionary (this incr. ref count of keyObj/valObj) */ + if (Tcl_DictObjPut(opts->interp, opts->mcDictObj, + keyObj, valObj) != TCL_OK + ) { + valObj = NULL; + goto done; + } + + Tcl_ResetResult(opts->interp); + + /* check special case - format object is not localizable */ + if (valObj == opts->formatObj) { + /* mark it as unlocalizable, by setting self as key (without refcount incr) */ + if (valObj->typePtr == &ClockFmtObjType) { + Tcl_UnsetObjRef(ObjLocFmtKey(valObj)); + ObjLocFmtKey(valObj) = valObj; + } + } + } + +done: + + Tcl_UnsetObjRef(keyObj); + return (opts->formatObj = valObj); +} + +/* + *---------------------------------------------------------------------- + * + * FindTokenBegin -- + * + * Find begin of given scan token in string, corresponding token type. + * + * Results: + * Position of token inside string if found. Otherwise - end of string. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static const char * +FindTokenBegin( + const char *p, + const char *end, + ClockScanToken *tok) +{ + char c; + if (p < end) { + /* next token a known token type */ + switch (tok->map->type) { + case CTOKT_INT: + case CTOKT_WIDE: + /* should match at least one digit */ + while (!isdigit(UCHAR(*p)) && (p = Tcl_UtfNext(p)) < end) {}; + return p; + break; + case CTOKT_WORD: + c = *(tok->tokWord.start); + /* should match at least to the first char of this word */ + while (*p != c && (p = Tcl_UtfNext(p)) < end) {}; + return p; + break; + case CTOKT_SPACE: + while (!isspace(UCHAR(*p)) && (p = Tcl_UtfNext(p)) < end) {}; + return p; + break; + case CTOKT_CHAR: + c = *((char *)tok->map->data); + while (*p != c && (p = Tcl_UtfNext(p)) < end) {}; + return p; + break; + } + } + return p; +} + +/* + *---------------------------------------------------------------------- + * + * DetermineGreedySearchLen -- + * + * Determine min/max lengths as exact as possible (speed, greedy match). + * + * Results: + * None. Lengths are stored in *minLenPtr, *maxLenPtr. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static void +DetermineGreedySearchLen( + DateInfo *info, ClockScanToken *tok, + int *minLenPtr, int *maxLenPtr) +{ + int minLen = tok->map->minSize; + int maxLen; + const char *p = yyInput + minLen, + *end = info->dateEnd; + + /* if still tokens available, try to correct minimum length */ + if ((tok+1)->map) { + end -= tok->endDistance + yySpaceCount; + /* find position of next known token */ + p = FindTokenBegin(p, end, tok+1); + if (p < end) { + minLen = p - yyInput; + } + } + + /* max length to the end regarding distance to end (min-width of following tokens) */ + maxLen = end - yyInput; + /* several amendments */ + if (maxLen > tok->map->maxSize) { + maxLen = tok->map->maxSize; + }; + if (minLen < tok->map->minSize) { + minLen = tok->map->minSize; + } + if (minLen > maxLen) { + maxLen = minLen; + } + if (maxLen > info->dateEnd - yyInput) { + maxLen = info->dateEnd - yyInput; + } + + /* check digits rigth now */ + if (tok->map->type == CTOKT_INT || tok->map->type == CTOKT_WIDE) { + p = yyInput; + end = p + maxLen; + if (end > info->dateEnd) { end = info->dateEnd; }; + while (isdigit(UCHAR(*p)) && p < end) { p++; }; + maxLen = p - yyInput; + } + + /* try to get max length more precise for greedy match, + * check the next ahead token available there */ + if (minLen < maxLen && tok->lookAhTok) { + ClockScanToken *laTok = tok + tok->lookAhTok + 1; + p = yyInput + maxLen; + /* regards all possible spaces here (because they are optional) */ + end = p + tok->lookAhMax + yySpaceCount + 1; + if (end > info->dateEnd) { + end = info->dateEnd; + } + p += tok->lookAhMin; + if (laTok->map && p < end) { + const char *f; + /* try to find laTok between [lookAhMin, lookAhMax] */ + while (minLen < maxLen) { + f = FindTokenBegin(p, end, laTok); + /* if found (not below lookAhMax) */ + if (f < end) { + break; + } + /* try again with fewer length */ + maxLen--; + p--; + end--; + } + } else if (p > end) { + maxLen -= (p - end); + if (maxLen < minLen) { + maxLen = minLen; + } + } + } + + *minLenPtr = minLen; + *maxLenPtr = maxLen; +} + +/* + *---------------------------------------------------------------------- + * + * ObjListSearch -- + * + * Find largest part of the input string from start regarding min and + * max lengths in the given list (utf-8, case sensitive). + * + * Results: + * TCL_OK - match found, TCL_RETURN - not matched, TCL_ERROR in error case. + * + * Side effects: + * Input points to end of the found token in string. + * + *---------------------------------------------------------------------- + */ + +static inline int +ObjListSearch( + DateInfo *info, int *val, + Tcl_Obj **lstv, int lstc, + int minLen, int maxLen) +{ + int i, l, lf = -1; + const char *s, *f, *sf; + /* search in list */ + for (i = 0; i < lstc; i++) { + s = TclGetString(lstv[i]); + l = lstv[i]->length; + + 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; + int 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; + int 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; + int 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) +{ + int 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.era = CE; + } else { + yydate.era = BCE; + } + + 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(ClockFmtScnCmdArgs *opts, + DateInfo *info, ClockScanToken *tok) +{ + int minLen, maxLen; + const char *p = yyInput, *end; const char *s; + Tcl_WideInt intJD; int fractJD = 0, fractJDDiv = 1; + (void)opts; + + 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 = + -210866803200L + + ( 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(ClockFmtScnCmdArgs *opts, + 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 "; + (void)opts; + + 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.era = CE; + 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 = + -210866803200L + + ( 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, TclOffset(DateInfo, date.dayOfMonth), + NULL, NULL}, + /* %m %N */ + {CTOKT_INT, CLF_MONTH, 0, 1, 2, TclOffset(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, TclOffset(DateInfo, date.year), + NULL, NULL}, + /* %Y */ + {CTOKT_INT, CLF_YEAR | CLF_CENTURY, 0, 4, 4, TclOffset(DateInfo, date.year), + NULL, NULL}, + /* %H %k %I %l */ + {CTOKT_INT, CLF_TIME, 0, 1, 2, TclOffset(DateInfo, date.hour), + NULL, NULL}, + /* %M */ + {CTOKT_INT, CLF_TIME, 0, 1, 2, TclOffset(DateInfo, date.minutes), + NULL, NULL}, + /* %S */ + {CTOKT_INT, CLF_TIME, 0, 1, 2, TclOffset(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, TclOffset(DateInfo, date.julianDay), + NULL, NULL}, + /* %j */ + {CTOKT_INT, CLF_DAYOFYEAR, 0, 1, 3, TclOffset(DateInfo, date.dayOfYear), + NULL, NULL}, + /* %C */ + {CTOKT_INT, CLF_CENTURY|CLF_ISO8601CENTURY, 0, 1, 2, TclOffset(DateInfo, dateCentury), + NULL, NULL}, + /* %g */ + {CTOKT_INT, CLF_ISO8601YEAR, 0, 2, 2, TclOffset(DateInfo, date.iso8601Year), + NULL, NULL}, + /* %G */ + {CTOKT_INT, CLF_ISO8601YEAR | CLF_ISO8601CENTURY, 0, 4, 4, TclOffset(DateInfo, date.iso8601Year), + NULL, NULL}, + /* %V */ + {CTOKT_INT, CLF_ISO8601WEAK, 0, 1, 2, TclOffset(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, TclOffset(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, TclOffset(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, TclOffset(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, TclOffset(DateInfo, date.dayOfMonth), + ClockScnToken_LocaleListMatcher_Proc, (void *)MCLIT_LOCALE_NUMERALS}, + /* %Om */ + {CTOKT_PARSER, CLF_MONTH, 0, 0, 0xffff, TclOffset(DateInfo, date.month), + ClockScnToken_LocaleListMatcher_Proc, (void *)MCLIT_LOCALE_NUMERALS}, + /* %Oy */ + {CTOKT_PARSER, CLF_YEAR, 0, 0, 0xffff, TclOffset(DateInfo, date.year), + ClockScnToken_LocaleListMatcher_Proc, (void *)MCLIT_LOCALE_NUMERALS}, + /* %OH %Ok %OI %Ol */ + {CTOKT_PARSER, CLF_TIME, 0, 0, 0xffff, TclOffset(DateInfo, date.hour), + ClockScnToken_LocaleListMatcher_Proc, (void *)MCLIT_LOCALE_NUMERALS}, + /* %OM */ + {CTOKT_PARSER, CLF_TIME, 0, 0, 0xffff, TclOffset(DateInfo, date.minutes), + ClockScnToken_LocaleListMatcher_Proc, (void *)MCLIT_LOCALE_NUMERALS}, + /* %OS */ + {CTOKT_PARSER, CLF_TIME, 0, 0, 0xffff, TclOffset(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)ckrealloc((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 *)ckalloc(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 *)ckrealloc(scnTok, tokCnt * sizeof(*tok))) != NULL ) { + scnTok = tok; + } + } + + /* now we're ready - assign now to storage (note the threaded race condition) */ + fss->scnTok = scnTok; + fss->scnTokC = tokCnt; + } +done: + Tcl_MutexUnlock(&ClockFmtMutex); + + return fss; +} + +/* + *---------------------------------------------------------------------- + */ +int +ClockScan( + DateInfo *info, /* Date fields used for parsing & converting */ + Tcl_Obj *strObj, /* String containing the time to scan */ + ClockFmtScnCmdArgs *opts) /* Command options */ +{ + ClockClientData *dataPtr = (ClockClientData *)opts->clientData; + ClockFmtScnStorage *fss; + ClockScanToken *tok; + ClockScanTokenMap *map; + const char *p, *x, *end; + unsigned short int flags = 0; + int ret = TCL_ERROR; + + /* get localized format */ + if (ClockLocalizeFormat(opts) == NULL) { + return TCL_ERROR; + } + + if ( !(fss = ClockGetOrParseScanFormat(opts->interp, opts->formatObj)) + || !(tok = fss->scnTok) + ) { + return TCL_ERROR; + } + + /* prepare parsing */ + + yyMeridian = MER24; + + p = TclGetString(strObj); + end = p + strObj->length; + /* in strict mode - bypass spaces at begin / end only (not between tokens) */ + if (opts->flags & CLF_STRICT) { + while (p < end && isspace(UCHAR(*p))) { + p++; + } + } + yyInput = p; + /* look ahead to count spaces (bypass it by count length and distances) */ + x = end; + while (p < end) { + if (isspace(UCHAR(*p))) { + x = ++p; /* after first space in space block */ + yySpaceCount++; + while (p < end && isspace(UCHAR(*p))) { + p++; + yySpaceCount++; + } + continue; + } + x = end; + p++; + } + /* ignore more as 1 space at end */ + yySpaceCount -= (end - x); + end = x; + /* ignore mandatory spaces used in format */ + yySpaceCount -= fss->scnSpaceCount; + if (yySpaceCount < 0) { + yySpaceCount = 0; + } + info->dateStart = p = yyInput; + info->dateEnd = end; + + /* parse string */ + for (; tok->map != NULL; tok++) { + map = tok->map; + /* bypass spaces at begin of input before parsing each token */ + if ( !(opts->flags & CLF_STRICT) + && ( map->type != CTOKT_SPACE + && map->type != CTOKT_WORD + && map->type != CTOKT_CHAR ) + ) { + while (p < end && isspace(UCHAR(*p))) { + yySpaceCount--; + p++; + } + } + yyInput = p; + /* end of input string */ + if (p >= end) { + break; + } + switch (map->type) + { + case CTOKT_INT: + case CTOKT_WIDE: + if (1) { + int minLen, size; + int sign = 1; + if (map->flags & CLF_SIGNED) { + if (*p == '+') { yyInput = ++p; } + else + if (*p == '-') { yyInput = ++p; sign = -1; }; + } + + DetermineGreedySearchLen(info, tok, &minLen, &size); + + if (size < map->minSize) { + /* missing input -> error */ + if ((map->flags & CLF_OPTIONAL)) { + continue; + } + goto not_match; + } + /* string 2 number, put number into info structure by offset */ + if (map->offs) { + p = yyInput; x = p + size; + if (map->type == CTOKT_INT) { + if (_str2int((int *)(((char *)info) + map->offs), + p, x, sign) != TCL_OK) { + goto overflow; + } + p = x; + } else { + if (_str2wideInt((Tcl_WideInt *)(((char *)info) + map->offs), + p, x, sign) != TCL_OK) { + goto overflow; + } + p = x; + } + flags = (flags & ~map->clearFlags) | map->flags; + } + } + break; + case CTOKT_PARSER: + switch (map->parser(opts, info, tok)) { + case TCL_OK: + break; + case TCL_RETURN: + if ((map->flags & CLF_OPTIONAL)) { + yyInput = p; + continue; + } + goto not_match; + break; + default: + goto done; + break; + }; + /* decrement count for possible spaces in match */ + while (p < yyInput) { + if (isspace(UCHAR(*p))) { + yySpaceCount--; + } + p++; + } + p = yyInput; + flags = (flags & ~map->clearFlags) | map->flags; + break; + case CTOKT_SPACE: + /* at least one space */ + if (!isspace(UCHAR(*p))) { + /* unmatched -> error */ + goto not_match; + } + /* don't decrement yySpaceCount by regular (first expected space), + * already considered above with fss->scnSpaceCount */; + p++; + while (p < end && isspace(UCHAR(*p))) { + yySpaceCount--; + p++; + } + break; + case CTOKT_WORD: + x = FindWordEnd(tok, p, end); + if (!x) { + /* no match -> error */ + goto not_match; + } + p = x; + break; + case CTOKT_CHAR: + x = (char *)map->data; + if (*x != *p) { + /* no match -> error */ + goto not_match; + } + if (isspace(UCHAR(*x))) { + yySpaceCount--; + } + p++; + break; + } + } + /* check end was reached */ + if (p < end) { + /* in non-strict mode bypass spaces at end of input */ + if ( !(opts->flags & CLF_STRICT) && isspace(UCHAR(*p)) ) { + p++; + while (p < end && isspace(UCHAR(*p))) { + p++; + } + } + /* something after last token - wrong format */ + if (p < end) { + goto not_match; + } + } + /* end of string, check only optional tokens at end, otherwise - not match */ + while (tok->map != NULL) { + if (!(opts->flags & CLF_STRICT) && (tok->map->type == CTOKT_SPACE)) { + tok++; + if (tok->map == NULL) { + /* no tokens anymore - trailing spaces are mandatory */ + goto not_match; + } + } + if (!(tok->map->flags & CLF_OPTIONAL)) { + goto not_match; + } + tok++; + } + + /* + * Invalidate result + */ + flags |= info->flags; + + /* seconds token (%s) take precedence over all other tokens */ + if ((opts->flags & CLF_EXTENDED) || !(flags & CLF_POSIXSEC)) { + if (flags & CLF_DATE) { + + if (!(flags & CLF_JULIANDAY)) { + info->flags |= CLF_ASSEMBLE_SECONDS|CLF_ASSEMBLE_JULIANDAY; + + /* dd precedence below ddd */ + switch (flags & (CLF_MONTH|CLF_DAYOFYEAR|CLF_DAYOFMONTH)) { + case (CLF_DAYOFYEAR|CLF_DAYOFMONTH): + /* miss month: ddd over dd (without month) */ + flags &= ~CLF_DAYOFMONTH; + /* fallthrough */ + case (CLF_DAYOFYEAR): + /* ddd over naked weekday */ + if (!(flags & CLF_ISO8601YEAR)) { + flags &= ~CLF_ISO8601WEAK; + } + break; + case (CLF_MONTH|CLF_DAYOFYEAR|CLF_DAYOFMONTH): + /* both available: mmdd over ddd */ + case (CLF_MONTH|CLF_DAYOFMONTH): + case (CLF_DAYOFMONTH): + /* mmdd / dd over naked weekday */ + if (!(flags & CLF_ISO8601YEAR)) { + flags &= ~CLF_ISO8601WEAK; + } + break; + /* neither mmdd nor ddd available */ + case 0: + /* but we have day of the week, which can be used */ + if (flags & CLF_DAYOFWEEK) { + /* prefer week based calculation of julianday */ + flags |= CLF_ISO8601WEAK; + } + } + + /* YearWeekDay below YearMonthDay */ + if ( (flags & CLF_ISO8601WEAK) + && ( (flags & (CLF_YEAR|CLF_DAYOFYEAR)) == (CLF_YEAR|CLF_DAYOFYEAR) + || (flags & (CLF_YEAR|CLF_DAYOFMONTH|CLF_MONTH)) == (CLF_YEAR|CLF_DAYOFMONTH|CLF_MONTH) + ) + ) { + /* yy precedence below yyyy */ + if (!(flags & CLF_ISO8601CENTURY) && (flags & CLF_CENTURY)) { + /* normally precedence of ISO is higher, but no century - so put it down */ + flags &= ~CLF_ISO8601WEAK; + } + else + /* yymmdd or yyddd over naked weekday */ + if (!(flags & CLF_ISO8601YEAR)) { + flags &= ~CLF_ISO8601WEAK; + } + } + + if ( (flags & CLF_YEAR) ) { + if (yyYear < 100) { + if (!(flags & CLF_CENTURY)) { + if (yyYear >= dataPtr->yearOfCenturySwitch) { + yyYear -= 100; + } + yyYear += dataPtr->currentYearCentury; + } else { + yyYear += info->dateCentury * 100; + } + } + } + if ( (flags & (CLF_ISO8601WEAK|CLF_ISO8601YEAR)) ) { + if ((flags & (CLF_ISO8601YEAR|CLF_YEAR)) == CLF_YEAR) { + /* for calculations expected iso year */ + info->date.iso8601Year = yyYear; + } + else + if (info->date.iso8601Year < 100) { + if (!(flags & CLF_ISO8601CENTURY)) { + if (info->date.iso8601Year >= dataPtr->yearOfCenturySwitch) { + info->date.iso8601Year -= 100; + } + info->date.iso8601Year += dataPtr->currentYearCentury; + } else { + info->date.iso8601Year += info->dateCentury * 100; + } + } + if ((flags & (CLF_ISO8601YEAR|CLF_YEAR)) == CLF_ISO8601YEAR) { + /* for calculations expected year (e. g. CLF_ISO8601WEAK not set) */ + yyYear = info->date.iso8601Year; + } + } + } + } + + /* if no time - reset time */ + if (!(flags & (CLF_TIME|CLF_LOCALSEC|CLF_POSIXSEC))) { + info->flags |= CLF_ASSEMBLE_SECONDS; + yydate.localSeconds = 0; + } + + if (flags & CLF_TIME) { + info->flags |= CLF_ASSEMBLE_SECONDS; + yySecondOfDay = ToSeconds(yyHour, yyMinutes, + yySeconds, yyMeridian); + } else + if (!(flags & (CLF_LOCALSEC|CLF_POSIXSEC))) { + info->flags |= CLF_ASSEMBLE_SECONDS; + yySecondOfDay = yydate.localSeconds % SECONDS_PER_DAY; + } + } + + /* tell caller which flags were set */ + info->flags |= flags; + + ret = TCL_OK; + goto done; + +overflow: + + Tcl_SetObjResult(opts->interp, Tcl_NewStringObj("integer value too large to represent", + -1)); + Tcl_SetErrorCode(opts->interp, "CLOCK", "dateTooLarge", (char *)NULL); + goto done; + +not_match: + + #if 1 + Tcl_SetObjResult(opts->interp, Tcl_NewStringObj("input string does not match supplied format", + -1)); + #else + /* to debug where exactly scan breaks */ + Tcl_SetObjResult(opts->interp, Tcl_ObjPrintf( + "input string \"%s\" does not match supplied format \"%s\"," + " locale \"%s\" - token \"%s\"", + info->dateStart, HashEntry4FmtScn(fss)->key.string, + Tcl_GetString(opts->localeObj), + tok && tok->tokWord.start ? tok->tokWord.start : "NULL")); + #endif + Tcl_SetErrorCode(opts->interp, "CLOCK", "badInputString", (char *)NULL); + +done: + + return ret; +} + +#define FrmResultIsAllocated(dateFmt) \ + (dateFmt->resEnd - dateFmt->resMem > MIN_FMT_RESULT_BLOCK_ALLOC) + +static inline int +FrmResultAllocate( + DateFormat *dateFmt, + int len) +{ + int needed = dateFmt->output + len - dateFmt->resEnd; + if (needed >= 0) { /* >= 0 - regards NTS zero */ + int newsize = dateFmt->resEnd - dateFmt->resMem + + needed + MIN_FMT_RESULT_BLOCK_ALLOC*2; + char *newRes; + /* differentiate between stack and memory */ + if (!FrmResultIsAllocated(dateFmt)) { + newRes = (char *)ckalloc(newsize); + if (newRes == NULL) { + return TCL_ERROR; + } + memcpy(newRes, dateFmt->resMem, dateFmt->output - dateFmt->resMem); + } else { + newRes = (char *)ckrealloc(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( + ClockFmtScnCmdArgs *opts, + DateFormat *dateFmt, + ClockFormatToken *tok, + int *val) +{ + (void)opts; + (void)dateFmt; + (void)tok; + + *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; + int 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 = TclGetString(mcObj); len = mcObj->length; + 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( + ClockFmtScnCmdArgs *opts, + DateFormat *dateFmt, + ClockFormatToken *tok, + int *val) + { + int fractYear; + /* Get day of year, zero based */ + int v = dateFmt->date.dayOfYear - 1; + (void)opts; + (void)tok; + (void)val; + + /* 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( + ClockFmtScnCmdArgs *opts, + DateFormat *dateFmt, + ClockFormatToken *tok, + int *val) +{ + int dow = dateFmt->date.dayOfWeek; + (void)opts; + + 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( + ClockFmtScnCmdArgs *opts, + DateFormat *dateFmt, + ClockFormatToken *tok, + int *val) + { + Tcl_WideInt intJD = dateFmt->date.julianDay; + int fractJD; + (void)opts; + (void)val; + + /* 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, + int *val) +{ + (void)val; + + 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; int 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 = TclGetString(objPtr); + len = objPtr->length; + 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, + ClockFormatToken *tok, + int *val) +{ + Tcl_Obj *mcObj; + const char *s; + int len; + (void)tok; + (void)val; + + if (dateFmt->date.era == BCE) { + mcObj = ClockMCGet(opts, MCLIT_BCE); + } else { + mcObj = ClockMCGet(opts, MCLIT_CE); + } + if (mcObj == NULL) { + return TCL_ERROR; + } + s = TclGetString(mcObj); len = mcObj->length; + 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) +{ + int 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; + int 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 = TclGetString(objPtr); + len = objPtr->length; + 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, TclOffset(DateFormat, date.dayOfMonth), NULL, NULL}, + /* %e */ + {CTOKT_INT, " ", 2, 0, 0, 0, TclOffset(DateFormat, date.dayOfMonth), NULL, NULL}, + /* %m */ + {CTOKT_INT, "0", 2, 0, 0, 0, TclOffset(DateFormat, date.month), NULL, NULL}, + /* %N */ + {CTOKT_INT, " ", 2, 0, 0, 0, TclOffset(DateFormat, date.month), NULL, NULL}, + /* %b %h */ + {CTOKT_INT, NULL, 0, CLFMT_LOCALE_INDX | CLFMT_DECR, 0, 12, TclOffset(DateFormat, date.month), + NULL, (void *)MCLIT_MONTHS_ABBREV}, + /* %B */ + {CTOKT_INT, NULL, 0, CLFMT_LOCALE_INDX | CLFMT_DECR, 0, 12, TclOffset(DateFormat, date.month), + NULL, (void *)MCLIT_MONTHS_FULL}, + /* %y */ + {CTOKT_INT, "0", 2, 0, 0, 100, TclOffset(DateFormat, date.year), NULL, NULL}, + /* %Y */ + {CTOKT_INT, "0", 4, 0, 0, 0, TclOffset(DateFormat, date.year), NULL, NULL}, + /* %C */ + {CTOKT_INT, "0", 2, 0, 100, 0, TclOffset(DateFormat, date.year), NULL, NULL}, + /* %H */ + {CTOKT_INT, "0", 2, 0, 3600, 24, TclOffset(DateFormat, date.secondOfDay), NULL, NULL}, + /* %M */ + {CTOKT_INT, "0", 2, 0, 60, 60, TclOffset(DateFormat, date.secondOfDay), NULL, NULL}, + /* %S */ + {CTOKT_INT, "0", 2, 0, 0, 60, TclOffset(DateFormat, date.secondOfDay), NULL, NULL}, + /* %I */ + {CTOKT_INT, "0", 2, CLFMT_CALC, 0, 0, TclOffset(DateFormat, date.secondOfDay), + ClockFmtToken_HourAMPM_Proc, NULL}, + /* %k */ + {CTOKT_INT, " ", 2, 0, 3600, 24, TclOffset(DateFormat, date.secondOfDay), NULL, NULL}, + /* %l */ + {CTOKT_INT, " ", 2, CLFMT_CALC, 0, 0, TclOffset(DateFormat, date.secondOfDay), + ClockFmtToken_HourAMPM_Proc, NULL}, + /* %p %P */ + {CTOKT_INT, NULL, 0, 0, 0, 0, TclOffset(DateFormat, date.secondOfDay), + ClockFmtToken_AMPM_Proc, NULL}, + /* %a */ + {CTOKT_INT, NULL, 0, CLFMT_LOCALE_INDX, 0, 7, TclOffset(DateFormat, date.dayOfWeek), + NULL, (void *)MCLIT_DAYS_OF_WEEK_ABBREV}, + /* %A */ + {CTOKT_INT, NULL, 0, CLFMT_LOCALE_INDX, 0, 7, TclOffset(DateFormat, date.dayOfWeek), + NULL, (void *)MCLIT_DAYS_OF_WEEK_FULL}, + /* %u */ + {CTOKT_INT, " ", 1, 0, 0, 0, TclOffset(DateFormat, date.dayOfWeek), NULL, NULL}, + /* %w */ + {CTOKT_INT, " ", 1, 0, 0, 7, TclOffset(DateFormat, date.dayOfWeek), NULL, NULL}, + /* %U %W */ + {CTOKT_INT, "0", 2, CLFMT_CALC, 0, 0, TclOffset(DateFormat, date.dayOfYear), + ClockFmtToken_WeekOfYear_Proc, NULL}, + /* %V */ + {CTOKT_INT, "0", 2, 0, 0, 0, TclOffset(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, TclOffset(DateFormat, date.iso8601Year), NULL, NULL}, + /* %G */ + {CTOKT_INT, "0", 4, 0, 0, 0, TclOffset(DateFormat, date.iso8601Year), NULL, NULL}, + /* %j */ + {CTOKT_INT, "0", 3, 0, 0, 0, TclOffset(DateFormat, date.dayOfYear), NULL, NULL}, + /* %J */ + {CTOKT_WIDE, "0", 7, 0, 0, 0, TclOffset(DateFormat, date.julianDay), NULL, NULL}, + /* %s */ + {CTOKT_WIDE, "0", 1, 0, 0, 0, TclOffset(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, TclOffset(DateFormat, date.year), + ClockFmtToken_LocaleERAYear_Proc, NULL}, + /* %Es */ + {CTOKT_WIDE, "0", 1, 0, 0, 0, TclOffset(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, TclOffset(DateFormat, date.dayOfMonth), + NULL, (void *)MCLIT_LOCALE_NUMERALS}, + /* %Om */ + {CTOKT_INT, NULL, 0, CLFMT_LOCALE_INDX, 0, 100, TclOffset(DateFormat, date.month), + NULL, (void *)MCLIT_LOCALE_NUMERALS}, + /* %Oy */ + {CTOKT_INT, NULL, 0, CLFMT_LOCALE_INDX, 0, 100, TclOffset(DateFormat, date.year), + NULL, (void *)MCLIT_LOCALE_NUMERALS}, + /* %OH %Ok */ + {CTOKT_INT, NULL, 0, CLFMT_LOCALE_INDX, 3600, 24, TclOffset(DateFormat, date.secondOfDay), + NULL, (void *)MCLIT_LOCALE_NUMERALS}, + /* %OI %Ol */ + {CTOKT_INT, NULL, 0, CLFMT_CALC | CLFMT_LOCALE_INDX, 0, 0, TclOffset(DateFormat, date.secondOfDay), + ClockFmtToken_HourAMPM_Proc, (void *)MCLIT_LOCALE_NUMERALS}, + /* %OM */ + {CTOKT_INT, NULL, 0, CLFMT_LOCALE_INDX, 60, 60, TclOffset(DateFormat, date.secondOfDay), + NULL, (void *)MCLIT_LOCALE_NUMERALS}, + /* %OS */ + {CTOKT_INT, NULL, 0, CLFMT_LOCALE_INDX, 0, 60, TclOffset(DateFormat, date.secondOfDay), + NULL, (void *)MCLIT_LOCALE_NUMERALS}, + /* %Ou */ + {CTOKT_INT, NULL, 0, CLFMT_LOCALE_INDX, 0, 100, TclOffset(DateFormat, date.dayOfWeek), + NULL, (void *)MCLIT_LOCALE_NUMERALS}, + /* %Ow */ + {CTOKT_INT, NULL, 0, CLFMT_LOCALE_INDX, 0, 7, TclOffset(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 *)ckalloc(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 *)ckrealloc(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 *)ckalloc(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) { + int 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) { + ckfree(dateFmt->resMem); + } + dateFmt->resMem = NULL; + +done: + + if (dateFmt->resMem) { + size_t size; + Tcl_Obj * result = Tcl_NewObj(); + result->length = dateFmt->output - dateFmt->resMem; + size = result->length+1; + if (dateFmt->resMem == resMem) { + result->bytes = (char *)ckalloc(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 *)ckrealloc(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( + ClientData clientData) /* Not used. */ +{ + (void)clientData; + 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: + */ Index: generic/tclDate.c ================================================================== --- generic/tclDate.c +++ generic/tclDate.c @@ -1,10 +1,11 @@ -/* A Bison parser, made by GNU Bison 3.1. */ +/* A Bison parser, made by GNU Bison 3.5.0. */ /* Bison implementation for Yacc-like parsers in C - Copyright (C) 1984, 1989-1990, 2000-2015, 2018 Free Software Foundation, Inc. + Copyright (C) 1984, 1989-1990, 2000-2015, 2018-2019 Free Software Foundation, + Inc. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. @@ -38,15 +39,18 @@ variables, as they might otherwise be expanded by user macros. There are some unavoidable exceptions within include files to define necessary library symbols; they are noted "INFRINGES ON USER NAME SPACE" below. */ +/* Undocumented macros, especially those whose name start with YY_, + are private implementation details. Do not rely on them. */ + /* Identify Bison output. */ #define YYBISON 1 /* Bison version. */ -#define YYBISON_VERSION "3.1" +#define YYBISON_VERSION "3.5.0" /* Skeleton name. */ #define YYSKELETON_NAME "yacc.c" /* Pure parsers. */ @@ -63,13 +67,11 @@ #define yyparse TclDateparse #define yylex TclDatelex #define yyerror TclDateerror #define yydebug TclDatedebug - -/* Copy the first part of user declarations. */ - +/* First part of user prologue. */ /* * tclDate.c -- * * This file is generated from a yacc grammar defined in the file @@ -91,90 +93,24 @@ #ifdef _MSC_VER #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 ckalloc #define YYFREE(x) (ckfree((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 /* @@ -186,18 +122,25 @@ #define HOUR(x) ((int) (60 * (x))) #define SECSPERDAY (24L * 60L * 60L) #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 { const char *name; int type; - time_t value; + Tcl_WideInt value; } TABLE; /* * Daylight-savings mode: on, off, or not yet known. */ @@ -206,16 +149,28 @@ DSTon, DSToff, DSTmaybe } DSTMODE; - +# ifndef YY_CAST +# ifdef __cplusplus +# define YY_CAST(Type, Val) static_cast (Val) +# define YY_REINTERPRET_CAST(Type, Val) reinterpret_cast (Val) +# else +# define YY_CAST(Type, Val) ((Type) (Val)) +# define YY_REINTERPRET_CAST(Type, Val) ((Type) (Val)) +# endif +# endif # ifndef YY_NULLPTR -# if defined __cplusplus && 201103L <= __cplusplus -# define YY_NULLPTR nullptr +# if defined __cplusplus +# if 201103L <= __cplusplus +# define YY_NULLPTR nullptr +# else +# define YY_NULLPTR 0 +# endif # else -# define YY_NULLPTR 0 +# define YY_NULLPTR ((void*)0) # endif # endif /* Enabling verbose error messages. */ #ifdef YYERROR_VERBOSE @@ -246,34 +201,35 @@ tMERIDIAN = 262, tMONTH = 263, tMONTH_UNIT = 264, tSTARDATE = 265, tSEC_UNIT = 266, - tSNUMBER = 267, - tUNUMBER = 268, - tZONE = 269, - tEPOCH = 270, - tDST = 271, - tISOBASE = 272, - tDAY_UNIT = 273, - tNEXT = 274 + tUNUMBER = 267, + tZONE = 268, + tZONEwO4 = 269, + tZONEwO2 = 270, + tEPOCH = 271, + tDST = 272, + tISOBAS8 = 273, + tISOBAS6 = 274, + tISOBASL = 275, + tDAY_UNIT = 276, + tNEXT = 277, + SP = 278 }; #endif /* Value type. */ #if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED - union YYSTYPE { - - time_t Number; + Tcl_WideInt Number; enum _MERIDIAN Meridian; }; - typedef union YYSTYPE YYSTYPE; # define YYSTYPE_IS_TRIVIAL 1 # define YYSTYPE_IS_DECLARED 1 #endif @@ -295,12 +251,11 @@ int TclDateparse (DateInfo* info); -/* Copy the second part of user declarations. */ - +/* Second part of user prologue. */ /* * Prototypes of internal functions. */ @@ -308,59 +263,116 @@ static int LookupWord(YYSTYPE* yylvalPtr, char *buff); static void TclDateerror(YYLTYPE* location, DateInfo* info, const char *s); 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*); #ifdef short # undef short #endif -#ifdef YYTYPE_UINT8 -typedef YYTYPE_UINT8 yytype_uint8; -#else -typedef unsigned char yytype_uint8; +/* On compilers that do not define __PTRDIFF_MAX__ etc., make sure + and (if available) are included + so that the code can choose integer types of a good width. */ + +#ifndef __PTRDIFF_MAX__ +# include /* INFRINGES ON USER NAME SPACE */ +# if defined __STDC_VERSION__ && 199901 <= __STDC_VERSION__ +# include /* INFRINGES ON USER NAME SPACE */ +# define YY_STDINT_H +# endif #endif -#ifdef YYTYPE_INT8 -typedef YYTYPE_INT8 yytype_int8; +/* Narrow types that promote to a signed type and that can represent a + signed or unsigned integer of at least N bits. In tables they can + save space and decrease cache pressure. Promoting to a signed type + helps avoid bugs in integer arithmetic. */ + +#ifdef __INT_LEAST8_MAX__ +typedef __INT_LEAST8_TYPE__ yytype_int8; +#elif defined YY_STDINT_H +typedef int_least8_t yytype_int8; #else typedef signed char yytype_int8; #endif -#ifdef YYTYPE_UINT16 -typedef YYTYPE_UINT16 yytype_uint16; -#else -typedef unsigned short yytype_uint16; -#endif - -#ifdef YYTYPE_INT16 -typedef YYTYPE_INT16 yytype_int16; +#ifdef __INT_LEAST16_MAX__ +typedef __INT_LEAST16_TYPE__ yytype_int16; +#elif defined YY_STDINT_H +typedef int_least16_t yytype_int16; #else typedef short yytype_int16; #endif + +#if defined __UINT_LEAST8_MAX__ && __UINT_LEAST8_MAX__ <= __INT_MAX__ +typedef __UINT_LEAST8_TYPE__ yytype_uint8; +#elif (!defined __UINT_LEAST8_MAX__ && defined YY_STDINT_H \ + && UINT_LEAST8_MAX <= INT_MAX) +typedef uint_least8_t yytype_uint8; +#elif !defined __UINT_LEAST8_MAX__ && UCHAR_MAX <= INT_MAX +typedef unsigned char yytype_uint8; +#else +typedef short yytype_uint8; +#endif + +#if defined __UINT_LEAST16_MAX__ && __UINT_LEAST16_MAX__ <= __INT_MAX__ +typedef __UINT_LEAST16_TYPE__ yytype_uint16; +#elif (!defined __UINT_LEAST16_MAX__ && defined YY_STDINT_H \ + && UINT_LEAST16_MAX <= INT_MAX) +typedef uint_least16_t yytype_uint16; +#elif !defined __UINT_LEAST16_MAX__ && USHRT_MAX <= INT_MAX +typedef unsigned short yytype_uint16; +#else +typedef int yytype_uint16; +#endif + +#ifndef YYPTRDIFF_T +# if defined __PTRDIFF_TYPE__ && defined __PTRDIFF_MAX__ +# define YYPTRDIFF_T __PTRDIFF_TYPE__ +# define YYPTRDIFF_MAXIMUM __PTRDIFF_MAX__ +# elif defined PTRDIFF_MAX +# ifndef ptrdiff_t +# include /* INFRINGES ON USER NAME SPACE */ +# endif +# define YYPTRDIFF_T ptrdiff_t +# define YYPTRDIFF_MAXIMUM PTRDIFF_MAX +# else +# define YYPTRDIFF_T long +# define YYPTRDIFF_MAXIMUM LONG_MAX +# endif +#endif #ifndef YYSIZE_T # ifdef __SIZE_TYPE__ # define YYSIZE_T __SIZE_TYPE__ # elif defined size_t # define YYSIZE_T size_t -# elif ! defined YYSIZE_T +# elif defined __STDC_VERSION__ && 199901 <= __STDC_VERSION__ # include /* INFRINGES ON USER NAME SPACE */ # define YYSIZE_T size_t # else # define YYSIZE_T unsigned # endif #endif -#define YYSIZE_MAXIMUM ((YYSIZE_T) -1) +#define YYSIZE_MAXIMUM \ + YY_CAST (YYPTRDIFF_T, \ + (YYPTRDIFF_MAXIMUM < YY_CAST (YYSIZE_T, -1) \ + ? YYPTRDIFF_MAXIMUM \ + : YY_CAST (YYSIZE_T, -1))) + +#define YYSIZEOF(X) YY_CAST (YYPTRDIFF_T, sizeof (X)) + +/* Stored state numbers (used for stacks). */ +typedef yytype_int8 yy_state_t; + +/* State numbers in computations. */ +typedef int yy_state_fast_t; #ifndef YY_ # if defined YYENABLE_NLS && YYENABLE_NLS # if ENABLE_NLS # include /* INFRINGES ON USER NAME SPACE */ @@ -370,34 +382,23 @@ # ifndef YY_ # define YY_(Msgid) Msgid # endif #endif -#ifndef YY_ATTRIBUTE -# if (defined __GNUC__ \ - && (2 < __GNUC__ || (__GNUC__ == 2 && 96 <= __GNUC_MINOR__))) \ - || defined __SUNPRO_C && 0x5110 <= __SUNPRO_C -# define YY_ATTRIBUTE(Spec) __attribute__(Spec) +#ifndef YY_ATTRIBUTE_PURE +# if defined __GNUC__ && 2 < __GNUC__ + (96 <= __GNUC_MINOR__) +# define YY_ATTRIBUTE_PURE __attribute__ ((__pure__)) # else -# define YY_ATTRIBUTE(Spec) /* empty */ +# define YY_ATTRIBUTE_PURE # endif #endif -#ifndef YY_ATTRIBUTE_PURE -# define YY_ATTRIBUTE_PURE YY_ATTRIBUTE ((__pure__)) -#endif - #ifndef YY_ATTRIBUTE_UNUSED -# define YY_ATTRIBUTE_UNUSED YY_ATTRIBUTE ((__unused__)) -#endif - -#if !defined _Noreturn \ - && (!defined __STDC_VERSION__ || __STDC_VERSION__ < 201112) -# if defined _MSC_VER && 1200 <= _MSC_VER -# define _Noreturn __declspec (noreturn) +# if defined __GNUC__ && 2 < __GNUC__ + (7 <= __GNUC_MINOR__) +# define YY_ATTRIBUTE_UNUSED __attribute__ ((__unused__)) # else -# define _Noreturn YY_ATTRIBUTE ((__noreturn__)) +# define YY_ATTRIBUTE_UNUSED # endif #endif /* Suppress unused-variable warnings by "using" E. */ #if ! defined lint || defined __GNUC__ @@ -406,15 +407,15 @@ # define YYUSE(E) /* empty */ #endif #if defined __GNUC__ && ! defined __ICC && 407 <= __GNUC__ * 100 + __GNUC_MINOR__ /* Suppress an incorrect diagnostic about yylval being uninitialized. */ -# define YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN \ - _Pragma ("GCC diagnostic push") \ - _Pragma ("GCC diagnostic ignored \"-Wuninitialized\"")\ +# define YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN \ + _Pragma ("GCC diagnostic push") \ + _Pragma ("GCC diagnostic ignored \"-Wuninitialized\"") \ _Pragma ("GCC diagnostic ignored \"-Wmaybe-uninitialized\"") -# define YY_IGNORE_MAYBE_UNINITIALIZED_END \ +# define YY_IGNORE_MAYBE_UNINITIALIZED_END \ _Pragma ("GCC diagnostic pop") #else # define YY_INITIAL_VALUE(Value) Value #endif #ifndef YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN @@ -423,10 +424,24 @@ #endif #ifndef YY_INITIAL_VALUE # define YY_INITIAL_VALUE(Value) /* Nothing. */ #endif +#if defined __cplusplus && defined __GNUC__ && ! defined __ICC && 6 <= __GNUC__ +# define YY_IGNORE_USELESS_CAST_BEGIN \ + _Pragma ("GCC diagnostic push") \ + _Pragma ("GCC diagnostic ignored \"-Wuseless-cast\"") +# define YY_IGNORE_USELESS_CAST_END \ + _Pragma ("GCC diagnostic pop") +#endif +#ifndef YY_IGNORE_USELESS_CAST_BEGIN +# define YY_IGNORE_USELESS_CAST_BEGIN +# define YY_IGNORE_USELESS_CAST_END +#endif + + +#define YY_ASSERT(E) ((void) (0 && (E))) #if ! defined yyoverflow || YYERROR_VERBOSE /* The parser invokes alloca or malloc; define the necessary symbols. */ @@ -500,22 +515,23 @@ && defined YYSTYPE_IS_TRIVIAL && YYSTYPE_IS_TRIVIAL))) /* A type that is properly aligned for any stack member. */ union yyalloc { - yytype_int16 yyss_alloc; + yy_state_t yyss_alloc; YYSTYPE yyvs_alloc; YYLTYPE yyls_alloc; }; /* The size of the maximum gap between one aligned stack and the next. */ -# define YYSTACK_GAP_MAXIMUM (sizeof (union yyalloc) - 1) +# define YYSTACK_GAP_MAXIMUM (YYSIZEOF (union yyalloc) - 1) /* The size of an array large to enough to hold all stacks, each with N elements. */ # define YYSTACK_BYTES(N) \ - ((N) * (sizeof (yytype_int16) + sizeof (YYSTYPE) + sizeof (YYLTYPE)) \ + ((N) * (YYSIZEOF (yy_state_t) + YYSIZEOF (YYSTYPE) \ + + YYSIZEOF (YYLTYPE)) \ + 2 * YYSTACK_GAP_MAXIMUM) # define YYCOPY_NEEDED 1 /* Relocate STACK from its old location to the new one. The @@ -524,15 +540,15 @@ stack. Advance YYPTR to a properly aligned location for the next stack. */ # define YYSTACK_RELOCATE(Stack_alloc, Stack) \ do \ { \ - YYSIZE_T yynewbytes; \ + YYPTRDIFF_T yynewbytes; \ YYCOPY (&yyptr->Stack_alloc, Stack, yysize); \ Stack = &yyptr->Stack_alloc; \ - yynewbytes = yystacksize * sizeof (*Stack) + YYSTACK_GAP_MAXIMUM; \ - yyptr += yynewbytes / sizeof (*yyptr); \ + yynewbytes = yystacksize * YYSIZEOF (*Stack) + YYSTACK_GAP_MAXIMUM; \ + yyptr += yynewbytes / YYSIZEOF (*yyptr); \ } \ while (0) #endif @@ -540,16 +556,16 @@ /* Copy COUNT objects from SRC to DST. The source and destination do not overlap. */ # ifndef YYCOPY # if defined __GNUC__ && 1 < __GNUC__ # define YYCOPY(Dst, Src, Count) \ - __builtin_memcpy (Dst, Src, (Count) * sizeof (*(Src))) + __builtin_memcpy (Dst, Src, YY_CAST (YYSIZE_T, (Count)) * sizeof (*(Src))) # else # define YYCOPY(Dst, Src, Count) \ do \ { \ - YYSIZE_T yyi; \ + YYPTRDIFF_T yyi; \ for (yyi = 0; yyi < (Count); yyi++) \ (Dst)[yyi] = (Src)[yyi]; \ } \ while (0) # endif @@ -557,42 +573,43 @@ #endif /* !YYCOPY_NEEDED */ /* 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 -/* YYTRANSLATE[YYX] -- Symbol number corresponding to YYX as returned - by yylex, with out-of-bounds checking. */ #define YYUNDEFTOK 2 -#define YYMAXUTOK 274 +#define YYMAXUTOK 278 + +/* YYTRANSLATE(TOKEN-NUM) -- Symbol number corresponding to TOKEN-NUM + as returned by yylex, with out-of-bounds checking. */ #define YYTRANSLATE(YYX) \ - ((unsigned) (YYX) <= YYMAXUTOK ? yytranslate[YYX] : YYUNDEFTOK) + (0 <= (YYX) && (YYX) <= YYMAXUTOK ? yytranslate[YYX] : YYUNDEFTOK) /* YYTRANSLATE[TOKEN-NUM] -- Symbol number corresponding to TOKEN-NUM - as returned by yylex, without out-of-bounds checking. */ -static const yytype_uint8 yytranslate[] = + as returned by yylex. */ +static const yytype_int8 yytranslate[] = { 0, 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, 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, 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, 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, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, @@ -607,23 +624,25 @@ 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, 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_uint16 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 +static const yytype_int16 yyrline[] = +{ + 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 #if YYDEBUG || YYERROR_VERBOSE || 0 /* YYTNAME[SYMBOL-NUM] -- String name of the symbol SYMBOL-NUM. @@ -630,148 +649,164 @@ First, the terminals, then, starting at YYNTOKENS, nonterminals. */ static const char *const yytname[] = { "$end", "error", "$undefined", "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 + "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 }; #endif # ifdef YYPRINT /* YYTOKNUM[NUM] -- (External) token number corresponding to the (internal) symbol number NUM (which must be that of a token). */ -static const yytype_uint16 yytoknum[] = +static const yytype_int16 yytoknum[] = { 0, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, - 58, 44, 47, 45, 46, 43 + 275, 276, 277, 278, 58, 44, 45, 47, 84, 46, + 43 }; # endif -#define YYPACT_NINF -18 - -#define yypact_value_is_default(Yystate) \ - (!!((Yystate) == (-18))) - -#define YYTABLE_NINF -1 - -#define yytable_value_is_error(Yytable_value) \ +#define YYPACT_NINF (-21) + +#define yypact_value_is_default(Yyn) \ + ((Yyn) == YYPACT_NINF) + +#define YYTABLE_NINF (-68) + +#define yytable_value_is_error(Yyn) \ 0 /* YYPACT[STATE-NUM] -- Index in YYTABLE of the portion describing 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. Performed when YYTABLE does not specify something else to do. Zero means the default is an error. */ -static const yytype_uint8 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 +static const yytype_int8 yydefact[] = +{ + 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[] = { - -1, 1, 17, 18, 19, 20, 21, 22, 23, 24, - 25, 26, 27, 28, 29, 66 + -1, 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 positive, shift that token. If negative, reduce the rule whose number is the opposite. If YYTABLE_NINF, syntax error. */ -static const yytype_uint8 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 +static const yytype_int8 yytable[] = +{ + 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_uint8 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 +static const yytype_int8 yycheck[] = +{ + 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 (internal number of the) accessing symbol of state STATE-NUM. */ -static const yytype_uint8 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 +static const yytype_int8 yystos[] = +{ + 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[YYN] -- Symbol number of symbol that rule YYN derives. */ -static const yytype_uint8 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 +static const yytype_int8 yyr1[] = +{ + 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[YYN] -- Number of symbols on the right hand side of rule YYN. */ -static const yytype_uint8 yyr2[] = +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 }; #define yyerrok (yyerrstatus = 0) #define yyclearin (yychar = YYEMPTY) @@ -783,26 +818,26 @@ #define YYERROR goto yyerrorlab #define YYRECOVERING() (!!yyerrstatus) -#define YYBACKUP(Token, Value) \ -do \ - if (yychar == YYEMPTY) \ - { \ - yychar = (Token); \ - yylval = (Value); \ - YYPOPSTACK (yylen); \ - yystate = *yyssp; \ - goto yybackup; \ - } \ - else \ - { \ - yyerror (&yylloc, info, YY_("syntax error: cannot back up")); \ - YYERROR; \ - } \ -while (0) +#define YYBACKUP(Token, Value) \ + do \ + if (yychar == YYEMPTY) \ + { \ + yychar = (Token); \ + yylval = (Value); \ + YYPOPSTACK (yylen); \ + yystate = *yyssp; \ + goto yybackup; \ + } \ + else \ + { \ + yyerror (&yylloc, info, YY_("syntax error: cannot back up")); \ + YYERROR; \ + } \ + while (0) /* Error token number */ #define YYTERROR 1 #define YYERRCODE 256 @@ -857,14 +892,14 @@ # if defined YYLTYPE_IS_TRIVIAL && YYLTYPE_IS_TRIVIAL /* Print *YYLOCP on YYO. Private, do not rely on its existence. */ YY_ATTRIBUTE_UNUSED -static unsigned +static int yy_location_print_ (FILE *yyo, YYLTYPE const * const yylocp) { - unsigned res = 0; + int res = 0; int end_col = 0 != yylocp->last_column ? yylocp->last_column - 1 : 0; if (0 <= yylocp->first_line) { res += YYFPRINTF (yyo, "%d", yylocp->first_line); if (0 <= yylocp->first_column) @@ -903,54 +938,56 @@ YYFPRINTF (stderr, "\n"); \ } \ } while (0) -/*----------------------------------------. -| Print this symbol's value on YYOUTPUT. | -`----------------------------------------*/ +/*-----------------------------------. +| Print this symbol's value on YYO. | +`-----------------------------------*/ static void -yy_symbol_value_print (FILE *yyoutput, int yytype, YYSTYPE const * const yyvaluep, YYLTYPE const * const yylocationp, DateInfo* info) +yy_symbol_value_print (FILE *yyo, int yytype, YYSTYPE const * const yyvaluep, YYLTYPE const * const yylocationp, DateInfo* info) { - FILE *yyo = yyoutput; - YYUSE (yyo); + FILE *yyoutput = yyo; + YYUSE (yyoutput); YYUSE (yylocationp); YYUSE (info); if (!yyvaluep) return; # ifdef YYPRINT if (yytype < YYNTOKENS) - YYPRINT (yyoutput, yytoknum[yytype], *yyvaluep); + YYPRINT (yyo, yytoknum[yytype], *yyvaluep); # endif + YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN YYUSE (yytype); + YY_IGNORE_MAYBE_UNINITIALIZED_END } -/*--------------------------------. -| Print this symbol on YYOUTPUT. | -`--------------------------------*/ +/*---------------------------. +| Print this symbol on YYO. | +`---------------------------*/ static void -yy_symbol_print (FILE *yyoutput, int yytype, YYSTYPE const * const yyvaluep, YYLTYPE const * const yylocationp, DateInfo* info) +yy_symbol_print (FILE *yyo, int yytype, YYSTYPE const * const yyvaluep, YYLTYPE const * const yylocationp, DateInfo* info) { - YYFPRINTF (yyoutput, "%s %s (", + YYFPRINTF (yyo, "%s %s (", yytype < YYNTOKENS ? "token" : "nterm", yytname[yytype]); - YY_LOCATION_PRINT (yyoutput, *yylocationp); - YYFPRINTF (yyoutput, ": "); - yy_symbol_value_print (yyoutput, yytype, yyvaluep, yylocationp, info); - YYFPRINTF (yyoutput, ")"); + YY_LOCATION_PRINT (yyo, *yylocationp); + YYFPRINTF (yyo, ": "); + yy_symbol_value_print (yyo, yytype, yyvaluep, yylocationp, info); + YYFPRINTF (yyo, ")"); } /*------------------------------------------------------------------. | yy_stack_print -- Print the state stack from its BOTTOM up to its | | TOP (included). | `------------------------------------------------------------------*/ static void -yy_stack_print (yytype_int16 *yybottom, yytype_int16 *yytop) +yy_stack_print (yy_state_t *yybottom, yy_state_t *yytop) { YYFPRINTF (stderr, "Stack now"); for (; yybottom <= yytop; yybottom++) { int yybot = *yybottom; @@ -969,24 +1006,24 @@ /*------------------------------------------------. | Report that the YYRULE is going to be reduced. | `------------------------------------------------*/ static void -yy_reduce_print (yytype_int16 *yyssp, YYSTYPE *yyvsp, YYLTYPE *yylsp, int yyrule, DateInfo* info) +yy_reduce_print (yy_state_t *yyssp, YYSTYPE *yyvsp, YYLTYPE *yylsp, int yyrule, DateInfo* info) { - unsigned long yylno = yyrline[yyrule]; + int yylno = yyrline[yyrule]; int yynrhs = yyr2[yyrule]; int yyi; - YYFPRINTF (stderr, "Reducing stack by rule %d (line %lu):\n", + YYFPRINTF (stderr, "Reducing stack by rule %d (line %d):\n", yyrule - 1, yylno); /* The symbols being reduced. */ for (yyi = 0; yyi < yynrhs; yyi++) { YYFPRINTF (stderr, " $%d = ", yyi + 1); yy_symbol_print (stderr, yystos[yyssp[yyi + 1 - yynrhs]], - &(yyvsp[(yyi + 1) - (yynrhs)]) + &yyvsp[(yyi + 1) - (yynrhs)] , &(yylsp[(yyi + 1) - (yynrhs)]) , info); YYFPRINTF (stderr, "\n"); } } @@ -1026,17 +1063,17 @@ #if YYERROR_VERBOSE # ifndef yystrlen # if defined __GLIBC__ && defined _STRING_H -# define yystrlen strlen +# define yystrlen(S) (YY_CAST (YYPTRDIFF_T, strlen (S))) # else /* Return the length of YYSTR. */ -static YYSIZE_T +static YYPTRDIFF_T yystrlen (const char *yystr) { - YYSIZE_T yylen; + YYPTRDIFF_T yylen; for (yylen = 0; yystr[yylen]; yylen++) continue; return yylen; } # endif @@ -1068,16 +1105,16 @@ heuristic is that double-quoting is unnecessary unless the string contains an apostrophe, a comma, or backslash (other than backslash-backslash). YYSTR is taken from yytname. If YYRES is null, do not copy; instead, return the length of what the result would have been. */ -static YYSIZE_T +static YYPTRDIFF_T yytnamerr (char *yyres, const char *yystr) { if (*yystr == '"') { - YYSIZE_T yyn = 0; + YYPTRDIFF_T yyn = 0; char const *yyp = yystr; for (;;) switch (*++yyp) { @@ -1086,11 +1123,14 @@ goto do_not_strip_quotes; case '\\': if (*++yyp != '\\') goto do_not_strip_quotes; - /* Fall through. */ + else + goto append; + + append: default: if (yyres) yyres[yyn] = *yyp; yyn++; break; @@ -1101,14 +1141,14 @@ return yyn; } do_not_strip_quotes: ; } - if (! yyres) + if (yyres) + return yystpcpy (yyres, yystr) - yyres; + else return yystrlen (yystr); - - return yystpcpy (yyres, yystr) - yyres; } # endif /* Copy into *YYMSG, which is of size *YYMSG_ALLOC, an error message about the unexpected token YYTOKEN for the state stack whose top is @@ -1117,23 +1157,23 @@ Return 0 if *YYMSG was successfully written. Return 1 if *YYMSG is not large enough to hold the message. In that case, also set *YYMSG_ALLOC to the required number of bytes. Return 2 if the required number of bytes is too large to store. */ static int -yysyntax_error (YYSIZE_T *yymsg_alloc, char **yymsg, - yytype_int16 *yyssp, int yytoken) +yysyntax_error (YYPTRDIFF_T *yymsg_alloc, char **yymsg, + yy_state_t *yyssp, int yytoken) { - YYSIZE_T yysize0 = yytnamerr (YY_NULLPTR, yytname[yytoken]); - YYSIZE_T yysize = yysize0; enum { YYERROR_VERBOSE_ARGS_MAXIMUM = 5 }; /* Internationalized format string. */ const char *yyformat = YY_NULLPTR; - /* Arguments of yyformat. */ + /* Arguments of yyformat: reported tokens (one for the "unexpected", + one per "expected"). */ char const *yyarg[YYERROR_VERBOSE_ARGS_MAXIMUM]; - /* Number of reported tokens (one for the "unexpected", one per - "expected"). */ + /* Actual size of YYARG. */ int yycount = 0; + /* Cumulated lengths of YYARG. */ + YYPTRDIFF_T yysize = 0; /* There are many possibilities here to consider: - If this state is a consistent state with a default action, then the only way this function was invoked is if the default action is an error action. In that case, don't check for expected @@ -1157,10 +1197,12 @@ accepted due to an error action in a later state. */ if (yytoken != YYEMPTY) { int yyn = yypact[*yyssp]; + YYPTRDIFF_T yysize0 = yytnamerr (YY_NULLPTR, yytname[yytoken]); + yysize = yysize0; yyarg[yycount++] = yytname[yytoken]; if (!yypact_value_is_default (yyn)) { /* Start YYX at -YYN if negative to avoid negative indexes in YYCHECK. In other words, skip the first -YYN actions for @@ -1181,15 +1223,16 @@ yysize = yysize0; break; } yyarg[yycount++] = yytname[yyx]; { - YYSIZE_T yysize1 = yysize + yytnamerr (YY_NULLPTR, yytname[yyx]); - if (! (yysize <= yysize1 - && yysize1 <= YYSTACK_ALLOC_MAXIMUM)) + YYPTRDIFF_T yysize1 + = yysize + yytnamerr (YY_NULLPTR, yytname[yyx]); + if (yysize <= yysize1 && yysize1 <= YYSTACK_ALLOC_MAXIMUM) + yysize = yysize1; + else return 2; - yysize = yysize1; } } } } @@ -1208,14 +1251,17 @@ YYCASE_(5, YY_("syntax error, unexpected %s, expecting %s or %s or %s or %s")); # undef YYCASE_ } { - YYSIZE_T yysize1 = yysize + yystrlen (yyformat); - if (! (yysize <= yysize1 && yysize1 <= YYSTACK_ALLOC_MAXIMUM)) + /* Don't count the "%s"s in the final size, but reserve room for + the terminator. */ + YYPTRDIFF_T yysize1 = yysize + (yystrlen (yyformat) - 2 * yycount) + 1; + if (yysize <= yysize1 && yysize1 <= YYSTACK_ALLOC_MAXIMUM) + yysize = yysize1; + else return 2; - yysize = yysize1; } if (*yymsg_alloc < yysize) { *yymsg_alloc = 2 * yysize; @@ -1237,12 +1283,12 @@ yyp += yytnamerr (yyp, yyarg[yyi++]); yyformat += 2; } else { - yyp++; - yyformat++; + ++yyp; + ++yyformat; } } return 0; } #endif /* YYERROR_VERBOSE */ @@ -1292,11 +1338,11 @@ = { 1, 1, 1, 1 } # endif ; YYLTYPE yylloc = yyloc_default; - int yystate; + yy_state_fast_t yystate; /* Number of tokens to shift before error messages enabled. */ int yyerrstatus; /* The stacks and their tools: 'yyss': related to states. @@ -1305,13 +1351,13 @@ Refer to the stacks through separate pointers, to allow yyoverflow to reallocate them elsewhere. */ /* The state stack. */ - yytype_int16 yyssa[YYINITDEPTH]; - yytype_int16 *yyss; - yytype_int16 *yyssp; + yy_state_t yyssa[YYINITDEPTH]; + yy_state_t *yyss; + yy_state_t *yyssp; /* The semantic value stack. */ YYSTYPE yyvsa[YYINITDEPTH]; YYSTYPE *yyvs; YYSTYPE *yyvsp; @@ -1322,11 +1368,11 @@ YYLTYPE *yylsp; /* The locations where the error started and ended. */ YYLTYPE yyerror_range[3]; - YYSIZE_T yystacksize; + YYPTRDIFF_T yystacksize; int yyn; int yyresult; /* Lookahead token as an internal (translated) token number. */ int yytoken = 0; @@ -1337,11 +1383,11 @@ #if YYERROR_VERBOSE /* Buffer for error messages, and its allocated size. */ char yymsgbuf[128]; char *yymsg = yymsgbuf; - YYSIZE_T yymsg_alloc = sizeof yymsgbuf; + YYPTRDIFF_T yymsg_alloc = sizeof yymsgbuf; #endif #define YYPOPSTACK(N) (yyvsp -= (N), yyssp -= (N), yylsp -= (N)) /* The number of symbols on the RHS of the reduced rule. @@ -1358,100 +1404,109 @@ yystate = 0; yyerrstatus = 0; yychar = YYEMPTY; /* Cause a token to be read. */ yylsp[0] = yylloc; goto yysetstate; + /*------------------------------------------------------------. -| yynewstate -- Push a new state, which is found in yystate. | +| yynewstate -- push a new state, which is found in yystate. | `------------------------------------------------------------*/ - yynewstate: +yynewstate: /* In all cases, when you get here, the value and location stacks have just been pushed. So pushing a state here evens the stacks. */ yyssp++; - yysetstate: - *yyssp = yystate; + +/*--------------------------------------------------------------------. +| yysetstate -- set current state (the top of the stack) to yystate. | +`--------------------------------------------------------------------*/ +yysetstate: + YYDPRINTF ((stderr, "Entering state %d\n", yystate)); + YY_ASSERT (0 <= yystate && yystate < YYNSTATES); + YY_IGNORE_USELESS_CAST_BEGIN + *yyssp = YY_CAST (yy_state_t, yystate); + YY_IGNORE_USELESS_CAST_END if (yyss + yystacksize - 1 <= yyssp) +#if !defined yyoverflow && !defined YYSTACK_RELOCATE + goto yyexhaustedlab; +#else { /* Get the current used size of the three stacks, in elements. */ - YYSIZE_T yysize = yyssp - yyss + 1; + YYPTRDIFF_T yysize = yyssp - yyss + 1; -#ifdef yyoverflow +# if defined yyoverflow { /* Give user a chance to reallocate the stack. Use copies of these so that the &'s don't force the real ones into memory. */ + yy_state_t *yyss1 = yyss; YYSTYPE *yyvs1 = yyvs; - yytype_int16 *yyss1 = yyss; YYLTYPE *yyls1 = yyls; /* Each stack pointer address is followed by the size of the data in use in that stack, in bytes. This used to be a conditional around just the two extra args, but that might be undefined if yyoverflow is a macro. */ yyoverflow (YY_("memory exhausted"), - &yyss1, yysize * sizeof (*yyssp), - &yyvs1, yysize * sizeof (*yyvsp), - &yyls1, yysize * sizeof (*yylsp), + &yyss1, yysize * YYSIZEOF (*yyssp), + &yyvs1, yysize * YYSIZEOF (*yyvsp), + &yyls1, yysize * YYSIZEOF (*yylsp), &yystacksize); - - yyls = yyls1; yyss = yyss1; yyvs = yyvs1; + yyls = yyls1; } -#else /* no yyoverflow */ -# ifndef YYSTACK_RELOCATE - goto yyexhaustedlab; -# else +# else /* defined YYSTACK_RELOCATE */ /* Extend the stack our own way. */ if (YYMAXDEPTH <= yystacksize) goto yyexhaustedlab; yystacksize *= 2; if (YYMAXDEPTH < yystacksize) yystacksize = YYMAXDEPTH; { - yytype_int16 *yyss1 = yyss; + yy_state_t *yyss1 = yyss; union yyalloc *yyptr = - (union yyalloc *) YYSTACK_ALLOC (YYSTACK_BYTES (yystacksize)); + YY_CAST (union yyalloc *, + YYSTACK_ALLOC (YY_CAST (YYSIZE_T, YYSTACK_BYTES (yystacksize)))); if (! yyptr) goto yyexhaustedlab; YYSTACK_RELOCATE (yyss_alloc, yyss); YYSTACK_RELOCATE (yyvs_alloc, yyvs); YYSTACK_RELOCATE (yyls_alloc, yyls); -# undef YYSTACK_RELOCATE +# undef YYSTACK_RELOCATE if (yyss1 != yyssa) YYSTACK_FREE (yyss1); } # endif -#endif /* no yyoverflow */ yyssp = yyss + yysize - 1; yyvsp = yyvs + yysize - 1; yylsp = yyls + yysize - 1; - YYDPRINTF ((stderr, "Stack size increased to %lu\n", - (unsigned long) yystacksize)); + YY_IGNORE_USELESS_CAST_BEGIN + YYDPRINTF ((stderr, "Stack size increased to %ld\n", + YY_CAST (long, yystacksize))); + YY_IGNORE_USELESS_CAST_END if (yyss + yystacksize - 1 <= yyssp) YYABORT; } - - YYDPRINTF ((stderr, "Entering state %d\n", yystate)); +#endif /* !defined yyoverflow && !defined YYSTACK_RELOCATE */ if (yystate == YYFINAL) YYACCEPT; goto yybackup; + /*-----------. | yybackup. | `-----------*/ yybackup: - /* Do appropriate processing given the current state. Read a lookahead token if we need one and don't already have one. */ /* First try to decide what to do without reference to lookahead token. */ yyn = yypact[yystate]; @@ -1497,19 +1552,18 @@ if (yyerrstatus) yyerrstatus--; /* Shift the lookahead token. */ YY_SYMBOL_PRINT ("Shifting", yytoken, &yylval, &yylloc); - - /* Discard the shifted token. */ - yychar = YYEMPTY; - yystate = yyn; YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN *++yyvsp = yylval; YY_IGNORE_MAYBE_UNINITIALIZED_END *++yylsp = yylloc; + + /* Discard the shifted token. */ + yychar = YYEMPTY; goto yynewstate; /*-----------------------------------------------------------. | yydefault -- do the default action for the current state. | @@ -1520,11 +1574,11 @@ goto yyerrlab; goto yyreduce; /*-----------------------------. -| yyreduce -- Do a reduction. | +| yyreduce -- do a reduction. | `-----------------------------*/ yyreduce: /* yyn is the number of a rule to reduce with. */ yylen = yyr2[yyn]; @@ -1542,366 +1596,306 @@ YYLLOC_DEFAULT (yyloc, (yylsp - yylen), yylen); yyerror_range[1] = yyloc; YY_REDUCE_PRINT (yyn); switch (yyn) { - case 4: - - { - yyHaveTime++; + case 4: + { + yyIncrFlags(CLF_TIME); } - break; case 5: - - { - yyHaveZone++; + { + yyIncrFlags(CLF_ZONE); } - break; case 6: - - { - yyHaveDate++; + { + yyIncrFlags(CLF_HAVEDATE); } - break; case 7: - - { - yyHaveOrdinalMonth++; + { + yyIncrFlags(CLF_ORDINALMONTH); } - break; case 8: - - { - yyHaveDay++; + { + yyIncrFlags(CLF_DAYOFWEEK); } - break; case 9: - - { - yyHaveRel++; + { + info->flags |= CLF_RELCONV; } - break; case 10: - - { - yyHaveTime++; - yyHaveDate++; + { + yyIncrFlags(CLF_TIME|CLF_HAVEDATE); } - break; case 11: - - { - yyHaveTime++; - yyHaveDate++; - yyHaveRel++; + { + yyIncrFlags(CLF_TIME|CLF_HAVEDATE); + info->flags |= CLF_RELCONV; } - break; case 13: + { + yyHour = (yyvsp[-4].Number); + yyMinutes = (yyvsp[-2].Number); + yySeconds = (yyvsp[0].Number); + } + break; - { + case 14: + { + yyHour = (yyvsp[-2].Number); + yyMinutes = (yyvsp[0].Number); + yySeconds = 0; + } + break; + + case 15: + { yyHour = (yyvsp[-1].Number); yyMinutes = 0; yySeconds = 0; yyMeridian = (yyvsp[0].Meridian); } - - break; - - case 14: - - { - yyHour = (yyvsp[-3].Number); - yyMinutes = (yyvsp[-1].Number); - yySeconds = 0; - yyMeridian = (yyvsp[0].Meridian); - } - - break; - - case 15: - - { - yyHour = (yyvsp[-5].Number); - yyMinutes = (yyvsp[-3].Number); - yySeconds = (yyvsp[-1].Number); - yyMeridian = (yyvsp[0].Meridian); - } - break; case 16: - - { - yyTimezone = (yyvsp[-1].Number); - if (yyTimezone > HOUR( 12)) yyTimezone -= HOUR(100); - yyDSTmode = DSTon; - } - - break; - - case 17: - - { - yyTimezone = (yyvsp[0].Number); - if (yyTimezone > HOUR( 12)) yyTimezone -= HOUR(100); - yyDSTmode = DSToff; - } - - break; - - case 18: - - { - yyTimezone = (yyvsp[0].Number); - yyDSTmode = DSTon; - } - - break; - - case 19: - - { + { + yyMeridian = (yyvsp[0].Meridian); + } + break; + + case 17: + { + yyTimezone = (yyvsp[-1].Number); + yyDSTmode = DSTon; + } + break; + + case 18: + { + yyTimezone = (yyvsp[0].Number); + yyDSTmode = DSToff; + } + break; + + case 19: + { + yyTimezone = (yyvsp[0].Number); + yyDSTmode = DSTon; + } + break; + + case 20: + { /* 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: + { /* GMT+1, GMT-10, etc. */ + yyTimezone = (yyvsp[-2].Number) - (yyvsp[-1].Number)*((yyvsp[0].Number) * 60); + yyDSTmode = DSToff; + } + break; + + case 22: + { /* +0100, -0100 */ yyTimezone = -(yyvsp[-1].Number)*((yyvsp[0].Number) % 100 + ((yyvsp[0].Number) / 100) * 60); yyDSTmode = DSToff; } - - break; - - case 20: - - { - yyDayOrdinal = 1; - yyDayNumber = (yyvsp[0].Number); - } - - break; - - case 21: - - { - yyDayOrdinal = 1; - yyDayNumber = (yyvsp[-1].Number); - } - - break; - - case 22: - - { - yyDayOrdinal = (yyvsp[-1].Number); - yyDayNumber = (yyvsp[0].Number); - } - - break; - - case 23: - - { - yyDayOrdinal = (yyvsp[-2].Number) * (yyvsp[-1].Number); - yyDayNumber = (yyvsp[0].Number); - } - - break; - - case 24: - - { - yyDayOrdinal = 2; - yyDayNumber = (yyvsp[0].Number); - } - break; case 25: + { + yyDayOrdinal = 1; + yyDayOfWeek = (yyvsp[0].Number); + } + break; - { + case 26: + { + yyDayOrdinal = 1; + yyDayOfWeek = (yyvsp[-1].Number); + } + break; + + case 27: + { + yyDayOrdinal = (yyvsp[-1].Number); + yyDayOfWeek = (yyvsp[0].Number); + } + break; + + case 28: + { + yyDayOrdinal = (yyvsp[-3].Number) * (yyvsp[-1].Number); + yyDayOfWeek = (yyvsp[0].Number); + } + break; + + case 29: + { + yyDayOrdinal = (yyvsp[-2].Number) * (yyvsp[-1].Number); + yyDayOfWeek = (yyvsp[0].Number); + } + break; + + case 30: + { + yyDayOrdinal = 2; + yyDayOfWeek = (yyvsp[0].Number); + } + break; + + case 31: + { + yyMonth = (yyvsp[-2].Number); + yyDay = (yyvsp[0].Number); + yyYear = (yyvsp[-4].Number); + } + break; + + case 32: + { yyMonth = (yyvsp[-2].Number); yyDay = (yyvsp[0].Number); } - break; - case 26: - - { + case 33: + { yyMonth = (yyvsp[-4].Number); yyDay = (yyvsp[-2].Number); yyYear = (yyvsp[0].Number); } - - break; - - case 27: - - { - yyYear = (yyvsp[0].Number) / 10000; - yyMonth = ((yyvsp[0].Number) % 10000)/100; - yyDay = (yyvsp[0].Number) % 100; - } - - break; - - case 28: - - { + break; + + case 35: + { yyDay = (yyvsp[-4].Number); yyMonth = (yyvsp[-2].Number); yyYear = (yyvsp[0].Number); } - - break; - - case 29: - - { - yyMonth = (yyvsp[-2].Number); - yyDay = (yyvsp[0].Number); - yyYear = (yyvsp[-4].Number); - } - - break; - - case 30: - - { + break; + + case 36: + { yyMonth = (yyvsp[-1].Number); yyDay = (yyvsp[0].Number); } - break; - case 31: - - { + case 37: + { yyMonth = (yyvsp[-3].Number); yyDay = (yyvsp[-2].Number); yyYear = (yyvsp[0].Number); } - break; - case 32: - - { + case 38: + { yyMonth = (yyvsp[0].Number); yyDay = (yyvsp[-1].Number); } - break; - case 33: - - { + case 39: + { yyMonth = 1; yyDay = 1; yyYear = EPOCH; } - break; - case 34: - - { + case 40: + { yyMonth = (yyvsp[-1].Number); yyDay = (yyvsp[-2].Number); yyYear = (yyvsp[0].Number); } - - break; - - case 35: - - { - yyMonthOrdinal = 1; - yyMonth = (yyvsp[0].Number); - } - - break; - - case 36: - - { - yyMonthOrdinal = (yyvsp[-1].Number); - yyMonth = (yyvsp[0].Number); - } - - break; - - case 37: - - { - 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); - } - - break; - - case 38: - - { - 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; + break; + + case 41: + { + yyMonthOrdinalIncr = 1; + yyMonthOrdinal = (yyvsp[0].Number); + } + break; + + case 42: + { + yyMonthOrdinalIncr = (yyvsp[-1].Number); + yyMonthOrdinal = (yyvsp[0].Number); + } + break; + + case 45: + { /* YYYYMMDD */ + yyYear = (yyvsp[0].Number) / 10000; + yyMonth = ((yyvsp[0].Number) % 10000)/100; + yyDay = (yyvsp[0].Number) % 100; + } + break; + + case 46: + { /* YYMMDD */ + yyYear = (yyvsp[0].Number) / 10000; + yyMonth = ((yyvsp[0].Number) % 10000)/100; + yyDay = (yyvsp[0].Number) % 100; + } + break; + + case 48: + { yyHour = (yyvsp[0].Number) / 10000; yyMinutes = ((yyvsp[0].Number) % 10000)/100; yySeconds = (yyvsp[0].Number) % 100; } - - break; - - case 39: - - { - 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); - } - - break; - - case 40: - - { + break; + + case 51: + { /* YYYYMMDDhhmmss */ yyYear = (yyvsp[-1].Number) / 10000; yyMonth = ((yyvsp[-1].Number) % 10000)/100; yyDay = (yyvsp[-1].Number) % 100; yyHour = (yyvsp[0].Number) / 10000; yyMinutes = ((yyvsp[0].Number) % 10000)/100; yySeconds = (yyvsp[0].Number) % 100; } + break; + case 52: + { /* 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 41: - - { + case 53: + { /* * Offset computed year by -377 so that the returned years will be * in a range accessible with a 32 bit clock seconds value. */ @@ -1909,113 +1903,113 @@ yyDay = 1; yyMonth = 1; yyRelDay += (((yyvsp[-2].Number)%1000)*(365 + IsLeapYear(yyYear)))/1000; yyRelSeconds += (yyvsp[0].Number) * 144 * 60; } - break; - case 42: - - { + case 54: + { yyRelSeconds *= -1; yyRelMonth *= -1; yyRelDay *= -1; } - - break; - - case 44: - - { - *yyRelPointer += (yyvsp[-2].Number) * (yyvsp[-1].Number) * (yyvsp[0].Number); - } - - break; - - case 45: - - { - *yyRelPointer += (yyvsp[-1].Number) * (yyvsp[0].Number); - } - - break; - - case 46: - - { - *yyRelPointer += (yyvsp[0].Number); - } - - break; - - case 47: - - { - *yyRelPointer += (yyvsp[-1].Number) * (yyvsp[0].Number); - } - - break; - - case 48: - - { - *yyRelPointer += (yyvsp[0].Number); - } - - break; - - case 49: - - { - (yyval.Number) = -1; - } - - break; - - case 50: - - { - (yyval.Number) = 1; - } - - break; - - case 51: - - { + break; + + case 56: + { + *yyRelPointer += (yyvsp[-3].Number) * (yyvsp[-1].Number) * (yyvsp[0].Number); + } + break; + + case 57: + { + *yyRelPointer += (yyvsp[-2].Number) * (yyvsp[-1].Number) * (yyvsp[0].Number); + } + break; + + case 58: + { + *yyRelPointer += (yyvsp[-1].Number) * (yyvsp[0].Number); + } + break; + + case 59: + { + *yyRelPointer += (yyvsp[0].Number); + } + break; + + case 60: + { + *yyRelPointer += (yyvsp[-1].Number) * (yyvsp[0].Number); + } + break; + + case 61: + { + *yyRelPointer += (yyvsp[0].Number); + } + break; + + case 62: + { + (yyval.Number) = -1; + } + break; + + case 63: + { + (yyval.Number) = 1; + } + break; + + case 64: + { (yyval.Number) = (yyvsp[0].Number); yyRelPointer = &yyRelSeconds; } - break; - case 52: - - { + case 65: + { (yyval.Number) = (yyvsp[0].Number); yyRelPointer = &yyRelDay; } - break; - case 53: - - { + case 66: + { (yyval.Number) = (yyvsp[0].Number); yyRelPointer = &yyRelMonth; } + break; + case 67: + { + (yyval.Number) = (yyvsp[0].Number); + } + break; + + case 68: + { + (yyval.Number) = (yyvsp[0].Number); + } + break; + + case 69: + { + (yyval.Number) = (yyvsp[0].Number); + } break; - case 54: - - { - if (yyHaveTime && yyHaveDate && !yyHaveRel) { + case 70: + { + 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; } else { yyHour = (yyvsp[0].Number) / 100; @@ -2023,27 +2017,22 @@ } yySeconds = 0; yyMeridian = MER24; } } - break; - case 55: - - { + case 71: + { (yyval.Meridian) = MER24; } - break; - case 56: - - { + case 72: + { (yyval.Meridian) = (yyvsp[0].Meridian); } - break; default: break; @@ -2069,18 +2058,17 @@ *++yylsp = yyloc; /* Now 'shift' the result of the reduction. Determine what state that goes to, based on the state we popped back to and the rule number reduced by. */ - - yyn = yyr1[yyn]; - - yystate = yypgoto[yyn - YYNTOKENS] + *yyssp; - if (0 <= yystate && yystate <= YYLAST && yycheck[yystate] == *yyssp) - yystate = yytable[yystate]; - else - yystate = yydefgoto[yyn - YYNTOKENS]; + { + const int yylhs = yyr1[yyn] - YYNTOKENS; + const int yyi = yypgoto[yylhs] + *yyssp; + yystate = (0 <= yyi && yyi <= YYLAST && yycheck[yyi] == *yyssp + ? yytable[yyi] + : yydefgoto[yylhs]); + } goto yynewstate; /*--------------------------------------. @@ -2107,11 +2095,11 @@ yymsgp = yymsg; else if (yysyntax_error_status == 1) { if (yymsg != yymsgbuf) YYSTACK_FREE (yymsg); - yymsg = (char *) YYSTACK_ALLOC (yymsg_alloc); + yymsg = YY_CAST (char *, YYSTACK_ALLOC (YY_CAST (YYSIZE_T, yymsg_alloc))); if (!yymsg) { yymsg = yymsgbuf; yymsg_alloc = sizeof yymsgbuf; yysyntax_error_status = 2; @@ -2158,16 +2146,14 @@ /*---------------------------------------------------. | yyerrorlab -- error raised explicitly by YYERROR. | `---------------------------------------------------*/ yyerrorlab: - - /* Pacify compilers like GCC when the user code never invokes - YYERROR and the label yyerrorlab therefore never appears in user - code. */ - if (/*CONSTCOND*/ 0) - goto yyerrorlab; + /* Pacify compilers when the user code never invokes YYERROR and the + label yyerrorlab therefore never appears in user code. */ + if (0) + YYERROR; /* Do not reclaim the symbols of the rule whose action triggered this YYERROR. */ YYPOPSTACK (yylen); yylen = 0; @@ -2229,17 +2215,19 @@ | yyacceptlab -- YYACCEPT comes here. | `-------------------------------------*/ yyacceptlab: yyresult = 0; goto yyreturn; + /*-----------------------------------. | yyabortlab -- YYABORT comes here. | `-----------------------------------*/ yyabortlab: yyresult = 1; goto yyreturn; + #if !defined yyoverflow || YYERROR_VERBOSE /*-------------------------------------------------. | yyexhaustedlab -- memory exhaustion comes here. | `-------------------------------------------------*/ @@ -2247,10 +2235,14 @@ yyerror (&yylloc, info, YY_("memory exhausted")); yyresult = 2; /* Fall through. */ #endif + +/*-----------------------------------------------------. +| yyreturn -- parsing is finished, return the result. | +`-----------------------------------------------------*/ yyreturn: if (yychar != YYEMPTY) { /* Make sure we have latest lookahead translation. See comments at user semantic actions for why this is necessary. */ @@ -2276,11 +2268,10 @@ if (yymsg != yymsgbuf) YYSTACK_FREE (yymsg); #endif return yyresult; } - /* * Month and day table. */ @@ -2395,10 +2386,12 @@ { "mdt", tDAYZONE, HOUR( 7) }, /* Mountain Daylight */ { "pst", tZONE, HOUR( 8) }, /* Pacific Standard */ { "pdt", tDAYZONE, HOUR( 8) }, /* Pacific Daylight */ { "yst", tZONE, HOUR( 9) }, /* Yukon Standard */ { "ydt", tDAYZONE, HOUR( 9) }, /* Yukon Daylight */ + { "akst", tZONE, HOUR( 9) }, /* Alaska Standard */ + { "akdt", tDAYZONE, HOUR( 9) }, /* Alaska Daylight */ { "hst", tZONE, HOUR(10) }, /* Hawaii Standard */ { "hdt", tDAYZONE, HOUR(10) }, /* Hawaii Daylight */ { "cat", tZONE, HOUR(10) }, /* Central Alaska */ { "ahst", tZONE, HOUR(10) }, /* Alaska-Hawaii Standard */ { "nt", tZONE, HOUR(11) }, /* Nome */ @@ -2451,37 +2444,47 @@ /* * Military timezone table. */ 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. */ @@ -2490,10 +2493,13 @@ YYLTYPE* location, DateInfo* infoPtr, 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); TclNewIntObj(t, location->first_column); Tcl_IncrRefCount(t); @@ -2506,15 +2512,15 @@ Tcl_DecrRefCount(t); Tcl_AppendToObj(infoPtr->messages, ")", -1); 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) { return -1; } @@ -2552,15 +2558,15 @@ * Make it lowercase. */ 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; } /* @@ -2670,54 +2676,118 @@ { char c; char *p; char buff[20]; int Count; + const char *tokStart; location->first_column = yyInput - info->dateStart; for ( ; ; ) { - while (TclIsSpaceProcM(*yyInput)) { - yyInput++; + + if (isspace(UCHAR(*yyInput))) { + yyInput = bypassSpaces(yyInput); + /* ignore space at end of text and before some words */ + c = *yyInput; + if (c != '\0' && !isalpha(UCHAR(c))) { + return SP; + } } + tokStart = yyInput; if (isdigit(UCHAR(c = *yyInput))) { /* INTL: digit */ + /* * Convert the string into a number; count the number of digits. */ - - Count = 0; - for (yylvalPtr->Number = 0; - isdigit(UCHAR(c = *yyInput++)); ) { /* INTL: digit */ - yylvalPtr->Number = 10 * yylvalPtr->Number + c - '0'; - Count++; - } - yyInput--; - yyDigitCount = Count; + int num = c - '0'; + p = (char *)yyInput; + while (isdigit(UCHAR(c = *(++p)))) { + if (num >= 0) { + num *= 10; num += c - '0'; + } + } + yylvalPtr->Number = num; + yyDigitCount = p - yyInput; + yyInput = p; /* * A number with 6 or more digits is considered an ISO 8601 base. */ - if (Count >= 6) { - location->last_column = yyInput - info->dateStart - 1; - return tISOBASE; - } else { - location->last_column = yyInput - info->dateStart - 1; - return tUNUMBER; + location->last_column = yyInput - info->dateStart - 1; + if (yyDigitCount >= 6) { + if (yyDigitCount == 14 || yyDigitCount == 12) { + /* long form of ISO 8601 (without separator), either + * YYYYMMDDhhmmss or YYYYMMDDhhmm, so reduce to date + * (8 chars is isodate) */ + p = (char *)tokStart; + num = *p++ - '0'; + do { + num *= 10; num += *p++ - '0'; + } while (p - tokStart < 8); + yylvalPtr->Number = num; + yyDigitCount = 8; + yyInput = p; + location->last_column = yyInput - info->dateStart - 1; + return tISOBASL; + } + if (num < 0) { /* overflow */ + return tID; + } + if (yyDigitCount == 8) { + return tISOBAS8; + } + if (yyDigitCount == 6) { + return tISOBAS6; + } } + /* ignore spaces after digits (optional) */ + yyInput = bypassSpaces(yyInput); + return tUNUMBER; } if (!(c & 0x80) && isalpha(UCHAR(c))) { /* INTL: ISO only. */ + int ret; for (p = buff; isalpha(UCHAR(c = *yyInput++)) /* INTL: ISO only. */ || c == '.'; ) { if (p < &buff[sizeof buff - 1]) { *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; return *yyInput++; } @@ -2733,177 +2803,83 @@ Count--; } } while (Count > 0); } } - -int -TclClockOldscanObjCmd( - void *dummy, /* Unused */ - Tcl_Interp *interp, /* Tcl interpreter */ - int objc, /* Count of parameters */ - Tcl_Obj *const *objv) /* Parameters */ -{ - Tcl_Obj *result, *resultElement; - int yr, mo, da; - DateInfo dateInfo; - DateInfo* info = &dateInfo; - int status; - (void)dummy; - - if (objc != 5) { - Tcl_WrongNumArgs(interp, 1, objv, - "stringToParse baseYear baseMonth baseDay" ); - return TCL_ERROR; - } - - yyInput = Tcl_GetString( objv[1] ); - dateInfo.dateStart = yyInput; - - yyHaveDate = 0; - if (Tcl_GetIntFromObj(interp, objv[2], &yr) != TCL_OK - || Tcl_GetIntFromObj(interp, objv[3], &mo) != TCL_OK - || Tcl_GetIntFromObj(interp, objv[4], &da) != TCL_OK) { - return TCL_ERROR; - } - yyYear = yr; yyMonth = mo; yyDay = da; - - yyHaveTime = 0; - yyHour = 0; yyMinutes = 0; yySeconds = 0; yyMeridian = MER24; - - yyHaveZone = 0; - yyTimezone = 0; yyDSTmode = DSTmaybe; - - yyHaveOrdinalMonth = 0; - yyMonthOrdinal = 0; - - yyHaveDay = 0; - yyDayOrdinal = 0; yyDayNumber = 0; - - yyHaveRel = 0; - yyRelMonth = 0; yyRelDay = 0; yyRelSeconds = 0; yyRelPointer = NULL; - - TclNewObj(dateInfo.messages); - dateInfo.separatrix = ""; - Tcl_IncrRefCount(dateInfo.messages); - - status = yyparse(&dateInfo); - if (status == 1) { - Tcl_SetObjResult(interp, dateInfo.messages); - Tcl_DecrRefCount(dateInfo.messages); - Tcl_SetErrorCode(interp, "TCL", "VALUE", "DATE", "PARSE", NULL); - return TCL_ERROR; + +int +TclClockFreeScan( + Tcl_Interp *interp, /* Tcl interpreter */ + DateInfo *info) /* Input and result parameters */ +{ + int status; + + #if YYDEBUG + /* enable debugging if compiled with YYDEBUG */ + yydebug = 1; + #endif + + /* + * yyInput = stringToParse; + * + * ClockInitDateInfo(info) should be executed to pre-init info; + */ + + yyDSTmode = DSTmaybe; + + info->separatrix = ""; + + info->dateStart = yyInput; + + /* ignore spaces at begin */ + yyInput = bypassSpaces(yyInput); + + /* parse */ + status = yyparse(info); + if (status == 1) { + 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", NULL); - return TCL_ERROR; + Tcl_SetErrorCode(interp, "TCL", "MEMORY", (char *)NULL); + 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", 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", 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", 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", 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", NULL); - return TCL_ERROR; - } - if (yyHaveOrdinalMonth > 1) { - Tcl_SetObjResult(interp, - Tcl_NewStringObj("more than one ordinal month in string", -1)); - Tcl_SetErrorCode(interp, "TCL", "VALUE", "DATE", "MULTIPLE", NULL); - return TCL_ERROR; - } - - TclNewObj(result); - TclNewObj(resultElement); - if (yyHaveDate) { - Tcl_ListObjAppendElement(interp, resultElement, - Tcl_NewIntObj((int) yyYear)); - Tcl_ListObjAppendElement(interp, resultElement, - Tcl_NewIntObj((int) yyMonth)); - Tcl_ListObjAppendElement(interp, resultElement, - Tcl_NewIntObj((int) yyDay)); - } - Tcl_ListObjAppendElement(interp, result, resultElement); - - if (yyHaveTime) { - Tcl_ListObjAppendElement(interp, result, Tcl_NewIntObj((int) - ToSeconds(yyHour, yyMinutes, yySeconds, (MERIDIAN)yyMeridian))); - } else { - Tcl_ListObjAppendElement(interp, result, Tcl_NewObj()); - } - - TclNewObj(resultElement); - if (yyHaveZone) { - Tcl_ListObjAppendElement(interp, resultElement, - Tcl_NewIntObj((int) -yyTimezone)); - Tcl_ListObjAppendElement(interp, resultElement, - Tcl_NewIntObj(1 - yyDSTmode)); - } - Tcl_ListObjAppendElement(interp, result, resultElement); - - TclNewObj(resultElement); - if (yyHaveRel) { - Tcl_ListObjAppendElement(interp, resultElement, - Tcl_NewIntObj((int) yyRelMonth)); - Tcl_ListObjAppendElement(interp, resultElement, - Tcl_NewIntObj((int) yyRelDay)); - Tcl_ListObjAppendElement(interp, resultElement, - Tcl_NewIntObj((int) yyRelSeconds)); - } - Tcl_ListObjAppendElement(interp, result, resultElement); - - TclNewObj(resultElement); - if (yyHaveDay && !yyHaveDate) { - Tcl_ListObjAppendElement(interp, resultElement, - Tcl_NewIntObj((int) yyDayOrdinal)); - Tcl_ListObjAppendElement(interp, resultElement, - Tcl_NewIntObj((int) yyDayNumber)); - } - Tcl_ListObjAppendElement(interp, result, resultElement); - - TclNewObj(resultElement); - if (yyHaveOrdinalMonth) { - Tcl_ListObjAppendElement(interp, resultElement, - Tcl_NewIntObj((int) yyMonthOrdinal)); - Tcl_ListObjAppendElement(interp, resultElement, - Tcl_NewIntObj((int) yyMonth)); - } - Tcl_ListObjAppendElement(interp, result, resultElement); - - Tcl_SetObjResult(interp, result); - return TCL_OK; + Tcl_SetErrorCode(interp, "TCL", "BUG", (char *)NULL); + status = TCL_ERROR; + } + if (info->messages) { + Tcl_DecrRefCount(info->messages); + } + return status; } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ ADDED generic/tclDate.h Index: generic/tclDate.h ================================================================== --- /dev/null +++ generic/tclDate.h @@ -0,0 +1,569 @@ +/* + * tclDate.h -- + * + * This header file handles common usage of clock primitives + * between tclDate.c (yacc), tclClock.c and tclClockFmt.c. + * + * Copyright (c) 2014 Serg G. Brester (aka sebres) + * + * See the file "license.terms" for information on usage and redistribution + * of this file, and for a DISCLAIMER OF ALL WARRANTIES. + */ + +#ifndef _TCLCLOCK_H +#define _TCLCLOCK_H + +/* + * Constants + */ + +#define JULIAN_DAY_POSIX_EPOCH 2440588 +#define GREGORIAN_CHANGE_DATE 2361222 +#define SECONDS_PER_DAY 86400 +#define JULIAN_SEC_POSIX_EPOCH (((Tcl_WideInt) JULIAN_DAY_POSIX_EPOCH) \ + * SECONDS_PER_DAY) +#define FOUR_CENTURIES 146097 /* days */ +#define JDAY_1_JAN_1_CE_JULIAN 1721424 +#define JDAY_1_JAN_1_CE_GREGORIAN 1721426 +#define ONE_CENTURY_GREGORIAN 36524 /* days */ +#define FOUR_YEARS 1461 /* days */ +#define ONE_YEAR 365 /* days */ + +#define RODDENBERRY 1946 /* Another epoch (Hi, Jeff!) */ + + +#define CLF_OPTIONAL (1 << 0) /* token is non mandatory */ +#define CLF_POSIXSEC (1 << 1) +#define CLF_LOCALSEC (1 << 2) +#define CLF_JULIANDAY (1 << 3) +#define CLF_TIME (1 << 4) +#define CLF_ZONE (1 << 5) +#define CLF_CENTURY (1 << 6) +#define CLF_DAYOFMONTH (1 << 7) +#define CLF_DAYOFYEAR (1 << 8) +#define CLF_MONTH (1 << 9) +#define CLF_YEAR (1 << 10) +#define CLF_DAYOFWEEK (1 << 11) +#define CLF_ISO8601YEAR (1 << 12) +#define CLF_ISO8601WEAK (1 << 13) +#define CLF_ISO8601CENTURY (1 << 14) + +#define CLF_SIGNED (1 << 15) + +/* extra flags used outside of scan/format-tokens too (int, not a short int) */ +#define CLF_RELCONV (1 << 17) +#define CLF_ORDINALMONTH (1 << 18) + +/* On demand (lazy) assemble flags */ +#define CLF_ASSEMBLE_DATE (1 << 28) /* assemble year, month, etc. using julianDay */ +#define CLF_ASSEMBLE_JULIANDAY (1 << 29) /* assemble julianDay using year, month, etc. */ +#define CLF_ASSEMBLE_SECONDS (1 << 30) /* assemble localSeconds (and seconds at end) */ + +#define CLF_HAVEDATE (CLF_DAYOFMONTH|CLF_MONTH|CLF_YEAR) +#define CLF_DATE (CLF_JULIANDAY | CLF_DAYOFMONTH | CLF_DAYOFYEAR | \ + CLF_MONTH | CLF_YEAR | CLF_ISO8601YEAR | \ + CLF_DAYOFWEEK | CLF_ISO8601WEAK) + +#define TCL_MIN_SECONDS -0x00F0000000000000L +#define TCL_MAX_SECONDS 0x00F0000000000000L +#define TCL_INV_SECONDS (TCL_MIN_SECONDS-1) + +/* + * Enumeration of the string literals used in [clock] + */ + +typedef enum ClockLiteral { + LIT__NIL, + LIT__DEFAULT_FORMAT, + LIT_SYSTEM, LIT_CURRENT, LIT_C, + LIT_BCE, LIT_CE, + LIT_DAYOFMONTH, LIT_DAYOFWEEK, LIT_DAYOFYEAR, + LIT_ERA, LIT_GMT, LIT_GREGORIAN, + LIT_INTEGER_VALUE_TOO_LARGE, + LIT_ISO8601WEEK, LIT_ISO8601YEAR, + LIT_JULIANDAY, LIT_LOCALSECONDS, + LIT_MONTH, + LIT_SECONDS, LIT_TZNAME, LIT_TZOFFSET, + LIT_YEAR, + LIT_TZDATA, + LIT_GETSYSTEMTIMEZONE, + LIT_SETUPTIMEZONE, + LIT_MCGET, + LIT_GETSYSTEMLOCALE, LIT_GETCURRENTLOCALE, + LIT_LOCALIZE_FORMAT, + LIT__END +} ClockLiteral; + +#define CLOCK_LITERAL_ARRAY(litarr) static const char *const litarr[] = { \ + "", \ + "%a %b %d %H:%M:%S %Z %Y", \ + "system", "current", "C", \ + "BCE", "CE", \ + "dayOfMonth", "dayOfWeek", "dayOfYear", \ + "era", ":GMT", "gregorian", \ + "integer value too large to represent", \ + "iso8601Week", "iso8601Year", \ + "julianDay", "localSeconds", \ + "month", \ + "seconds", "tzName", "tzOffset", \ + "year", \ + "::tcl::clock::TZData", \ + "::tcl::clock::GetSystemTimeZone", \ + "::tcl::clock::SetupTimeZone", \ + "::tcl::clock::mcget", \ + "::tcl::clock::GetSystemLocale", "::tcl::clock::mclocale", \ + "::tcl::clock::LocalizeFormat" \ +} + +/* + * Enumeration of the msgcat literals used in [clock] + */ + +typedef enum ClockMsgCtLiteral { + MCLIT__NIL, /* placeholder */ + MCLIT_MONTHS_FULL, MCLIT_MONTHS_ABBREV, MCLIT_MONTHS_COMB, + MCLIT_DAYS_OF_WEEK_FULL, MCLIT_DAYS_OF_WEEK_ABBREV, MCLIT_DAYS_OF_WEEK_COMB, + MCLIT_AM, MCLIT_PM, + MCLIT_LOCALE_ERAS, + MCLIT_BCE, MCLIT_CE, + MCLIT_BCE2, MCLIT_CE2, + MCLIT_BCE3, MCLIT_CE3, + MCLIT_LOCALE_NUMERALS, + MCLIT__END +} ClockMsgCtLiteral; + +#define CLOCK_LOCALE_LITERAL_ARRAY(litarr, pref) static const char *const litarr[] = { \ + pref "", \ + pref "MONTHS_FULL", pref "MONTHS_ABBREV", pref "MONTHS_COMB", \ + pref "DAYS_OF_WEEK_FULL", pref "DAYS_OF_WEEK_ABBREV", pref "DAYS_OF_WEEK_COMB", \ + pref "AM", pref "PM", \ + pref "LOCALE_ERAS", \ + pref "BCE", pref "CE", \ + pref "b.c.e.", pref "c.e.", \ + pref "b.c.", pref "a.d.", \ + pref "LOCALE_NUMERALS", \ +} + +/* + * Structure containing the fields used in [clock format] and [clock scan] + */ + +typedef enum {BCE=1, CE=0} ERA_ENUM; + +#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 */ + ERA_ENUM era; /* Era */ + int gregorian; /* Flag == 1 if the date is Gregorian */ + int year; /* Year of the era */ + int dayOfYear; /* Day of the year (1 January == 1) */ + int month; /* Month number */ + int dayOfMonth; /* Day of the month */ + int iso8601Year; /* ISO8601 week-based year */ + int iso8601Week; /* ISO8601 week number */ + int dayOfWeek; /* Day of the week */ + 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 \ + TclOffset(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 (1 << 2) +#define CLF_EXTENDED (1 << 4) +#define CLF_STRICT (1 << 8) +#define CLF_LOCALE_USED (1 << 15) + +typedef struct ClockFmtScnCmdArgs { + ClientData clientData; /* Opaque pointer to literal pool, etc. */ + Tcl_Interp *interp; /* Tcl interpreter */ + + Tcl_Obj *formatObj; /* Format */ + Tcl_Obj *localeObj; /* Name of the locale where the time will be expressed. */ + Tcl_Obj *timezoneObj; /* Default time zone in which the time will be expressed */ + Tcl_Obj *baseObj; /* Base (scan and add) or clockValue (format) */ + int flags; /* Flags control scanning */ + + Tcl_Obj *mcDictObj; /* Current dictionary of tcl::clock package for given localeObj*/ +} ClockFmtScnCmdArgs; + +/* Last-period cache for fast UTC to local and backwards conversion */ +typedef struct ClockLastTZOffs { + /* keys */ + Tcl_Obj *timezoneObj; + int changeover; + Tcl_WideInt localSeconds; + Tcl_WideInt rangesVal[2]; /* Bounds for cached time zone offset */ + /* values */ + int tzOffset; + Tcl_Obj *tzName; /* Name (abbreviation) of this area in TZ */ +} ClockLastTZOffs; + +/* + * Structure containing the client data for [clock] + */ + +typedef struct ClockClientData { + size_t refCount; /* Number of live references. */ + Tcl_Obj **literals; /* Pool of object literals (common, locale independent). */ + Tcl_Obj **mcLiterals; /* Msgcat object literals with mc-keys for search with locale. */ + Tcl_Obj **mcLitIdxs; /* Msgcat object indices prefixed with _IDX_, + * used for quick dictionary search */ + + Tcl_Obj *mcDicts; /* Msgcat collection, contains weak pointers to locale + * catalogs, and owns it references (onetime referenced) */ + + /* Cache for current clock parameters, imparted via "configure" */ + size_t lastTZEpoch; + int currentYearCentury; + int yearOfCenturySwitch; + int validMinYear; + int validMaxYear; + double maxJDN; + + Tcl_Obj *systemTimeZone; + Tcl_Obj *systemSetupTZData; + Tcl_Obj *gmtSetupTimeZoneUnnorm; + Tcl_Obj *gmtSetupTimeZone; + Tcl_Obj *gmtSetupTZData; + Tcl_Obj *gmtTZName; + Tcl_Obj *lastSetupTimeZoneUnnorm; + Tcl_Obj *lastSetupTimeZone; + Tcl_Obj *lastSetupTZData; + Tcl_Obj *prevSetupTimeZoneUnnorm; + Tcl_Obj *prevSetupTimeZone; + Tcl_Obj *prevSetupTZData; + + Tcl_Obj *defaultLocale; + Tcl_Obj *defaultLocaleDict; + Tcl_Obj *currentLocale; + Tcl_Obj *currentLocaleDict; + Tcl_Obj *lastUsedLocaleUnnorm; + Tcl_Obj *lastUsedLocale; + Tcl_Obj *lastUsedLocaleDict; + Tcl_Obj *prevUsedLocaleUnnorm; + Tcl_Obj *prevUsedLocale; + Tcl_Obj *prevUsedLocaleDict; + + /* Cache for last base (last-second fast convert if base/tz not changed) */ + struct { + Tcl_Obj *timezoneObj; + TclDateFields date; + } lastBase; + + /* Last-period cache for fast UTC to Local and backwards conversion */ + ClockLastTZOffs lastTZOffsCache[2]; + + int defFlags; /* Default flags (from configure), ATM + * only CLF_VALIDATE supported */ +} ClockClientData; + +#define ClockDefaultYearCentury 2000 +#define ClockDefaultCenturySwitch 38 + +/* + * Clock scan and format facilities. + */ + +#define CLOCK_FMT_SCN_STORAGE_GC_SIZE 32 + +#define CLOCK_MIN_TOK_CHAIN_BLOCK_SIZE 2 + +typedef struct ClockScanToken ClockScanToken; + + +typedef int ClockScanTokenProc( + ClockFmtScnCmdArgs *opts, + DateInfo *info, + ClockScanToken *tok); + + +typedef enum _CLCKTOK_TYPE { + CTOKT_INT = 1, CTOKT_WIDE, CTOKT_PARSER, CTOKT_SPACE, CTOKT_WORD, CTOKT_CHAR, + CFMTT_PROC +} CLCKTOK_TYPE; + +typedef struct ClockScanTokenMap { + unsigned short int type; + unsigned short int flags; + unsigned short int clearFlags; + unsigned short int minSize; + unsigned short int maxSize; + unsigned short int offs; + ClockScanTokenProc *parser; + const void *data; +} ClockScanTokenMap; + +struct ClockScanToken { + ClockScanTokenMap *map; + struct { + const char *start; + const char *end; + } tokWord; + unsigned short int endDistance; + unsigned short int lookAhMin; + unsigned short int lookAhMax; + unsigned short int lookAhTok; +}; + + +#define MIN_FMT_RESULT_BLOCK_ALLOC 80 +#define MIN_FMT_RESULT_BLOCK_DELTA 0 +/* Maximal permitted threshold (buffer size > result size) in percent, + * to directly return the buffer without reallocate */ +#define MAX_FMT_RESULT_THRESHOLD 2 + +typedef struct DateFormat { + char *resMem; + char *resEnd; + char *output; + + TclDateFields date; + + Tcl_Obj *localeEra; +} DateFormat; + +#define CLFMT_INCR (1 << 3) +#define CLFMT_DECR (1 << 4) +#define CLFMT_CALC (1 << 5) +#define CLFMT_LOCALE_INDX (1 << 8) + +typedef struct ClockFormatToken ClockFormatToken; + +typedef int ClockFormatTokenProc( + ClockFmtScnCmdArgs *opts, + DateFormat *dateFmt, + ClockFormatToken *tok, + int *val); + +typedef struct ClockFormatTokenMap { + unsigned short int type; + const char *tostr; + unsigned short int width; + unsigned short int flags; + unsigned short int divider; + unsigned short int divmod; + unsigned short int offs; + ClockFormatTokenProc *fmtproc; + void *data; +} ClockFormatTokenMap; + +struct ClockFormatToken { + ClockFormatTokenMap *map; + struct { + const char *start; + const char *end; + } tokWord; +}; + + +typedef struct ClockFmtScnStorage ClockFmtScnStorage; + +struct ClockFmtScnStorage { + int objRefCount; /* Reference count shared across threads */ + ClockScanToken *scnTok; + unsigned int scnTokC; + unsigned int scnSpaceCount; /* Count of mandatory spaces used in format */ + ClockFormatToken *fmtTok; + unsigned int fmtTokC; +#if CLOCK_FMT_SCN_STORAGE_GC_SIZE > 0 + ClockFmtScnStorage *nextPtr; + ClockFmtScnStorage *prevPtr; +#endif + size_t fmtMinAlloc; +#if 0 + +Tcl_HashEntry hashEntry /* ClockFmtScnStorage is a derivate of Tcl_HashEntry, + * stored by offset +sizeof(self) */ +#endif +}; + +/* + * Clock macros. + */ + +/* + * Extracts Julian day and seconds of the day from posix seconds (tm). + */ +#define ClockExtractJDAndSODFromSeconds(jd, sod, tm) \ + if (1) { \ + jd = (tm + JULIAN_SEC_POSIX_EPOCH); \ + if (jd >= SECONDS_PER_DAY || jd <= -SECONDS_PER_DAY) { \ + jd /= SECONDS_PER_DAY; \ + sod = (int)(tm % SECONDS_PER_DAY); \ + } else { \ + sod = (int)jd, jd = 0; \ + } \ + if (sod < 0) { \ + sod += SECONDS_PER_DAY; \ + /* JD is affected, if switched into negative (avoid 24 hours difference) */ \ + if (jd <= 0) { \ + jd--; \ + } \ + } \ + } + +/* + * Prototypes of module functions. + */ + +MODULE_SCOPE int ToSeconds(int Hours, int Minutes, + int Seconds, MERIDIAN Meridian); +MODULE_SCOPE int IsGregorianLeapYear(TclDateFields *); +MODULE_SCOPE void + GetJulianDayFromEraYearWeekDay( + TclDateFields *fields, int changeover); +MODULE_SCOPE void + GetJulianDayFromEraYearMonthDay( + TclDateFields *fields, int changeover); +MODULE_SCOPE void + GetJulianDayFromEraYearDay( + TclDateFields *fields, int changeover); +MODULE_SCOPE int ConvertUTCToLocal(ClientData clientData, Tcl_Interp *, + TclDateFields *, Tcl_Obj *timezoneObj, int); +MODULE_SCOPE Tcl_Obj * + LookupLastTransition(Tcl_Interp *, Tcl_WideInt, + int, Tcl_Obj *const *, Tcl_WideInt *rangesVal); + +MODULE_SCOPE int TclClockFreeScan(Tcl_Interp *interp, DateInfo *info); + +/* tclClock.c module declarations */ + +MODULE_SCOPE Tcl_Obj * + ClockSetupTimeZone(ClientData clientData, + Tcl_Interp *interp, Tcl_Obj *timezoneObj); + +MODULE_SCOPE Tcl_Obj * + ClockMCDict(ClockFmtScnCmdArgs *opts); +MODULE_SCOPE Tcl_Obj * + ClockMCGet(ClockFmtScnCmdArgs *opts, int mcKey); +MODULE_SCOPE Tcl_Obj * + ClockMCGetIdx(ClockFmtScnCmdArgs *opts, int mcKey); +MODULE_SCOPE int ClockMCSetIdx(ClockFmtScnCmdArgs *opts, int mcKey, + Tcl_Obj *valObj); + +/* tclClockFmt.c module declarations */ + + +MODULE_SCOPE char * + TclItoAw(char *buf, int val, char padchar, unsigned short int width); + +MODULE_SCOPE Tcl_Obj* + ClockFrmObjGetLocFmtKey(Tcl_Interp *interp, + Tcl_Obj *objPtr); + +MODULE_SCOPE ClockFmtScnStorage * + Tcl_GetClockFrmScnFromObj(Tcl_Interp *interp, + Tcl_Obj *objPtr); +MODULE_SCOPE Tcl_Obj * + ClockLocalizeFormat(ClockFmtScnCmdArgs *opts); + +MODULE_SCOPE int ClockScan(register DateInfo *info, + Tcl_Obj *strObj, ClockFmtScnCmdArgs *opts); + +MODULE_SCOPE int ClockFormat(register DateFormat *dateFmt, + ClockFmtScnCmdArgs *opts); + +MODULE_SCOPE void ClockFrmScnClearCaches(void); + +#endif /* _TCLCLOCK_H */ Index: generic/tclDictObj.c ================================================================== --- generic/tclDictObj.c +++ generic/tclDictObj.c @@ -1948,10 +1948,64 @@ } result = Tcl_DictObjSize(interp, objv[1], &size); if (result == TCL_OK) { Tcl_SetObjResult(interp, Tcl_NewIntObj(size)); } + return result; +} + +/* + *---------------------------------------------------------------------- + * + * 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; + } + + dict = DICT(dictPtr); + + result = Tcl_NewObj(); + DICT(result) = dict; + dict->refCount++; + result->internalRep.twoPtrValue.ptr2 = NULL; + result->typePtr = &tclDictType; + return result; } /* *---------------------------------------------------------------------- Index: generic/tclEnsemble.c ================================================================== --- generic/tclEnsemble.c +++ generic/tclEnsemble.c @@ -351,10 +351,18 @@ (permitPrefix ? TCL_ENSEMBLE_PREFIX : 0)); Tcl_SetEnsembleSubcommandList(interp, token, subcmdObj); Tcl_SetEnsembleMappingDict(interp, token, mapObj); Tcl_SetEnsembleUnknownHandler(interp, token, unknownObj); Tcl_SetEnsembleParameterList(interp, token, paramObj); + /* + * Ensemble should be compiled if it has map (performance purposes) + * Currently only for internal using namespace (like ::tcl::clock). + * (An enhancement for completelly compile-feature is in work.) + */ + if (mapObj != NULL && strncmp("::tcl::", nsPtr->fullName, 7) == 0) { + Tcl_SetEnsembleFlags(interp, token, ENSEMBLE_COMPILE); + } /* * Tricky! Must ensure that the result is not shared (command delete * traces could have corrupted the pristine object that we started * with). [Snit test rename-1.5] Index: generic/tclGetDate.y ================================================================== --- generic/tclGetDate.y +++ generic/tclGetDate.y @@ -7,10 +7,11 @@ * 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 (c) 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. */ @@ -43,90 +44,24 @@ #ifdef _MSC_VER #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 ckalloc #define YYFREE(x) (ckfree((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 /* @@ -138,18 +73,25 @@ #define HOUR(x) ((int) (60 * (x))) #define SECSPERDAY (24L * 60L * 60L) #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 { const char *name; int type; - time_t value; + Tcl_WideInt value; } TABLE; /* * Daylight-savings mode: on, off, or not yet known. */ @@ -159,11 +101,11 @@ } DSTMODE; %} %union { - time_t Number; + Tcl_WideInt Number; enum _MERIDIAN Meridian; } %{ @@ -174,12 +116,10 @@ static int LookupWord(YYSTYPE* yylvalPtr, char *buff); static void TclDateerror(YYLTYPE* location, DateInfo* info, const char *s); 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*); %} %token tAGO @@ -189,29 +129,37 @@ %token tMERIDIAN %token tMONTH %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 tDAY %type tDAYZONE %type tMONTH %type tMONTH_UNIT %type tDST %type tSEC_UNIT -%type tSNUMBER %type tUNUMBER +%type INTNUM %type tZONE -%type tISOBASE +%type tZONEwO4 +%type tZONEwO2 +%type tISOBAS8 +%type tISOBAS6 +%type tISOBASL %type tDAY_UNIT %type unit %type sign %type tNEXT %type tSTARDATE @@ -220,133 +168,145 @@ %% 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 ; +iextime : tUNUMBER ':' tUNUMBER ':' tUNUMBER { + yyHour = $1; + yyMinutes = $3; + yySeconds = $5; + } + | tUNUMBER ':' tUNUMBER { + yyHour = $1; + yyMinutes = $3; + yySeconds = 0; + } + ; time : tUNUMBER tMERIDIAN { yyHour = $1; yyMinutes = 0; yySeconds = 0; yyMeridian = $2; } - | tUNUMBER ':' tUNUMBER o_merid { - yyHour = $1; - yyMinutes = $3; - yySeconds = 0; - yyMeridian = $4; - } - | tUNUMBER ':' tUNUMBER ':' tUNUMBER o_merid { - yyHour = $1; - yyMinutes = $3; - yySeconds = $5; - yyMeridian = $6; + | 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; } | tUNUMBER '/' tUNUMBER '/' tUNUMBER { yyMonth = $1; 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; } | tUNUMBER tMONTH { @@ -364,58 +324,61 @@ yyYear = $3; } ; ordMonth: tNEXT tMONTH { - yyMonthOrdinal = 1; - yyMonth = $2; - } - | tNEXT tUNUMBER tMONTH { - yyMonthOrdinal = $2; - yyMonth = $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; - 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; - yyYear = $1 / 10000; - yyMonth = ($1 % 10000)/100; - yyDay = $1 % 100; - yyHour = $3; - yyMinutes = $5; - yySeconds = $7; - } - | tISOBASE tISOBASE { + yyMonthOrdinalIncr = 1; + yyMonthOrdinal = $2; + } + | tNEXT tUNUMBER tMONTH { + yyMonthOrdinalIncr = $2; + yyMonthOrdinal = $3; + } + ; + +isosep : 'T'|SP + ; +isodate : tISOBAS8 { /* YYYYMMDD */ + yyYear = $1 / 10000; + yyMonth = ($1 % 10000)/100; + yyDay = $1 % 100; + } + | tISOBAS6 { /* YYMMDD */ + yyYear = $1 / 10000; + yyMonth = ($1 % 10000)/100; + yyDay = $1 % 100; + } + | 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; yyHour = $2 / 10000; 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. */ @@ -433,20 +396,23 @@ yyRelDay *= -1; } | 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 { *yyRelPointer += $1; } @@ -472,15 +438,26 @@ $$ = $1; yyRelPointer = &yyRelMonth; } ; -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; } else { yyHour = $1 / 100; @@ -616,10 +593,12 @@ { "mdt", tDAYZONE, HOUR( 7) }, /* Mountain Daylight */ { "pst", tZONE, HOUR( 8) }, /* Pacific Standard */ { "pdt", tDAYZONE, HOUR( 8) }, /* Pacific Daylight */ { "yst", tZONE, HOUR( 9) }, /* Yukon Standard */ { "ydt", tDAYZONE, HOUR( 9) }, /* Yukon Daylight */ + { "akst", tZONE, HOUR( 9) }, /* Alaska Standard */ + { "akdt", tDAYZONE, HOUR( 9) }, /* Alaska Daylight */ { "hst", tZONE, HOUR(10) }, /* Hawaii Standard */ { "hdt", tDAYZONE, HOUR(10) }, /* Hawaii Daylight */ { "cat", tZONE, HOUR(10) }, /* Central Alaska */ { "ahst", tZONE, HOUR(10) }, /* Alaska-Hawaii Standard */ { "nt", tZONE, HOUR(11) }, /* Nome */ @@ -672,37 +651,47 @@ /* * Military timezone table. */ 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. */ @@ -711,10 +700,13 @@ YYLTYPE* location, DateInfo* infoPtr, 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); TclNewIntObj(t, location->first_column); Tcl_IncrRefCount(t); @@ -727,15 +719,15 @@ Tcl_DecrRefCount(t); Tcl_AppendToObj(infoPtr->messages, ")", -1); 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) { return -1; } @@ -773,15 +765,15 @@ * Make it lowercase. */ 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; } /* @@ -891,54 +883,118 @@ { char c; char *p; char buff[20]; int Count; + const char *tokStart; location->first_column = yyInput - info->dateStart; for ( ; ; ) { - while (TclIsSpaceProcM(*yyInput)) { - yyInput++; + + if (isspace(UCHAR(*yyInput))) { + yyInput = bypassSpaces(yyInput); + /* ignore space at end of text and before some words */ + c = *yyInput; + if (c != '\0' && !isalpha(UCHAR(c))) { + return SP; + } } + tokStart = yyInput; if (isdigit(UCHAR(c = *yyInput))) { /* INTL: digit */ + /* * Convert the string into a number; count the number of digits. */ - - Count = 0; - for (yylvalPtr->Number = 0; - isdigit(UCHAR(c = *yyInput++)); ) { /* INTL: digit */ - yylvalPtr->Number = 10 * yylvalPtr->Number + c - '0'; - Count++; - } - yyInput--; - yyDigitCount = Count; + int num = c - '0'; + p = (char *)yyInput; + while (isdigit(UCHAR(c = *(++p)))) { + if (num >= 0) { + num *= 10; num += c - '0'; + } + } + yylvalPtr->Number = num; + yyDigitCount = p - yyInput; + yyInput = p; /* * A number with 6 or more digits is considered an ISO 8601 base. */ - if (Count >= 6) { - location->last_column = yyInput - info->dateStart - 1; - return tISOBASE; - } else { - location->last_column = yyInput - info->dateStart - 1; - return tUNUMBER; + location->last_column = yyInput - info->dateStart - 1; + if (yyDigitCount >= 6) { + if (yyDigitCount == 14 || yyDigitCount == 12) { + /* long form of ISO 8601 (without separator), either + * YYYYMMDDhhmmss or YYYYMMDDhhmm, so reduce to date + * (8 chars is isodate) */ + p = (char *)tokStart; + num = *p++ - '0'; + do { + num *= 10; num += *p++ - '0'; + } while (p - tokStart < 8); + yylvalPtr->Number = num; + yyDigitCount = 8; + yyInput = p; + location->last_column = yyInput - info->dateStart - 1; + return tISOBASL; + } + if (num < 0) { /* overflow */ + return tID; + } + if (yyDigitCount == 8) { + return tISOBAS8; + } + if (yyDigitCount == 6) { + return tISOBAS6; + } } + /* ignore spaces after digits (optional) */ + yyInput = bypassSpaces(yyInput); + return tUNUMBER; } if (!(c & 0x80) && isalpha(UCHAR(c))) { /* INTL: ISO only. */ + int ret; for (p = buff; isalpha(UCHAR(c = *yyInput++)) /* INTL: ISO only. */ || c == '.'; ) { if (p < &buff[sizeof buff - 1]) { *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; return *yyInput++; } @@ -954,177 +1010,83 @@ Count--; } } while (Count > 0); } } - -int -TclClockOldscanObjCmd( - void *dummy, /* Unused */ - Tcl_Interp *interp, /* Tcl interpreter */ - int objc, /* Count of parameters */ - Tcl_Obj *const *objv) /* Parameters */ -{ - Tcl_Obj *result, *resultElement; - int yr, mo, da; - DateInfo dateInfo; - DateInfo* info = &dateInfo; - int status; - (void)dummy; - - if (objc != 5) { - Tcl_WrongNumArgs(interp, 1, objv, - "stringToParse baseYear baseMonth baseDay" ); - return TCL_ERROR; - } - - yyInput = Tcl_GetString( objv[1] ); - dateInfo.dateStart = yyInput; - - yyHaveDate = 0; - if (Tcl_GetIntFromObj(interp, objv[2], &yr) != TCL_OK - || Tcl_GetIntFromObj(interp, objv[3], &mo) != TCL_OK - || Tcl_GetIntFromObj(interp, objv[4], &da) != TCL_OK) { - return TCL_ERROR; - } - yyYear = yr; yyMonth = mo; yyDay = da; - - yyHaveTime = 0; - yyHour = 0; yyMinutes = 0; yySeconds = 0; yyMeridian = MER24; - - yyHaveZone = 0; - yyTimezone = 0; yyDSTmode = DSTmaybe; - - yyHaveOrdinalMonth = 0; - yyMonthOrdinal = 0; - - yyHaveDay = 0; - yyDayOrdinal = 0; yyDayNumber = 0; - - yyHaveRel = 0; - yyRelMonth = 0; yyRelDay = 0; yyRelSeconds = 0; yyRelPointer = NULL; - - TclNewObj(dateInfo.messages); - dateInfo.separatrix = ""; - Tcl_IncrRefCount(dateInfo.messages); - - status = yyparse(&dateInfo); - if (status == 1) { - Tcl_SetObjResult(interp, dateInfo.messages); - Tcl_DecrRefCount(dateInfo.messages); - Tcl_SetErrorCode(interp, "TCL", "VALUE", "DATE", "PARSE", NULL); - return TCL_ERROR; + +int +TclClockFreeScan( + Tcl_Interp *interp, /* Tcl interpreter */ + DateInfo *info) /* Input and result parameters */ +{ + int status; + + #if YYDEBUG + /* enable debugging if compiled with YYDEBUG */ + yydebug = 1; + #endif + + /* + * yyInput = stringToParse; + * + * ClockInitDateInfo(info) should be executed to pre-init info; + */ + + yyDSTmode = DSTmaybe; + + info->separatrix = ""; + + info->dateStart = yyInput; + + /* ignore spaces at begin */ + yyInput = bypassSpaces(yyInput); + + /* parse */ + status = yyparse(info); + if (status == 1) { + 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", NULL); - return TCL_ERROR; + Tcl_SetErrorCode(interp, "TCL", "MEMORY", (char *)NULL); + 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", 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", 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", 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", 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", NULL); - return TCL_ERROR; - } - if (yyHaveOrdinalMonth > 1) { - Tcl_SetObjResult(interp, - Tcl_NewStringObj("more than one ordinal month in string", -1)); - Tcl_SetErrorCode(interp, "TCL", "VALUE", "DATE", "MULTIPLE", NULL); - return TCL_ERROR; - } - - TclNewObj(result); - TclNewObj(resultElement); - if (yyHaveDate) { - Tcl_ListObjAppendElement(interp, resultElement, - Tcl_NewIntObj((int) yyYear)); - Tcl_ListObjAppendElement(interp, resultElement, - Tcl_NewIntObj((int) yyMonth)); - Tcl_ListObjAppendElement(interp, resultElement, - Tcl_NewIntObj((int) yyDay)); - } - Tcl_ListObjAppendElement(interp, result, resultElement); - - if (yyHaveTime) { - Tcl_ListObjAppendElement(interp, result, Tcl_NewIntObj((int) - ToSeconds(yyHour, yyMinutes, yySeconds, (MERIDIAN)yyMeridian))); - } else { - Tcl_ListObjAppendElement(interp, result, Tcl_NewObj()); - } - - TclNewObj(resultElement); - if (yyHaveZone) { - Tcl_ListObjAppendElement(interp, resultElement, - Tcl_NewIntObj((int) -yyTimezone)); - Tcl_ListObjAppendElement(interp, resultElement, - Tcl_NewIntObj(1 - yyDSTmode)); - } - Tcl_ListObjAppendElement(interp, result, resultElement); - - TclNewObj(resultElement); - if (yyHaveRel) { - Tcl_ListObjAppendElement(interp, resultElement, - Tcl_NewIntObj((int) yyRelMonth)); - Tcl_ListObjAppendElement(interp, resultElement, - Tcl_NewIntObj((int) yyRelDay)); - Tcl_ListObjAppendElement(interp, resultElement, - Tcl_NewIntObj((int) yyRelSeconds)); - } - Tcl_ListObjAppendElement(interp, result, resultElement); - - TclNewObj(resultElement); - if (yyHaveDay && !yyHaveDate) { - Tcl_ListObjAppendElement(interp, resultElement, - Tcl_NewIntObj((int) yyDayOrdinal)); - Tcl_ListObjAppendElement(interp, resultElement, - Tcl_NewIntObj((int) yyDayNumber)); - } - Tcl_ListObjAppendElement(interp, result, resultElement); - - TclNewObj(resultElement); - if (yyHaveOrdinalMonth) { - Tcl_ListObjAppendElement(interp, resultElement, - Tcl_NewIntObj((int) yyMonthOrdinal)); - Tcl_ListObjAppendElement(interp, resultElement, - Tcl_NewIntObj((int) yyMonth)); - } - Tcl_ListObjAppendElement(interp, result, resultElement); - - Tcl_SetObjResult(interp, result); - return TCL_OK; + Tcl_SetErrorCode(interp, "TCL", "BUG", (char *)NULL); + status = TCL_ERROR; + } + if (info->messages) { + Tcl_DecrRefCount(info->messages); + } + return status; } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ Index: generic/tclInt.h ================================================================== --- generic/tclInt.h +++ generic/tclInt.h @@ -2913,10 +2913,11 @@ MODULE_SCOPE void TclDeleteNamespaceVars(Namespace *nsPtr); MODULE_SCOPE int TclFindDictElement(Tcl_Interp *interp, const char *dict, int dictLength, const char **elementPtr, const char **nextPtr, int *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, int numBytes, int flags, int line, int *clNextOuter, const char *outerScript); MODULE_SCOPE Tcl_ObjCmdProc TclFileAttrsCmd; ADDED generic/tclStrIdxTree.c Index: generic/tclStrIdxTree.c ================================================================== --- /dev/null +++ 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; + int offs = 0; + + if (item == NULL) { + goto done; + } + + /* search in tree */ + do { + cinf = cin = TclGetString(item->key) + offs; + f = TclUtfFindEqualNCInLwr(s, end, cin, cin + item->length - offs, &cinf); + /* if something was found */ + if (f > s) { + /* if whole string was found */ + if (f >= end) { + start = f; + goto done; + }; + /* set new offset and shift start string */ + offs += cinf - cin; + s = f; + /* if match item, go deeper as long as possible */ + if (offs >= item->length && item->childTree.firstPtr) { + /* save previuosly found item (if not ambigous) for + * possible fallback (few greedy match) */ + if (item->value != NULL) { + prevf = f; + prevItem = item; + prevParent = parent; + } + parent = &item->childTree; + item = item->childTree.firstPtr; + continue; + } + /* no children - return this item and current chars found */ + start = f; + goto done; + } + + item = item->nextPtr; + + } while (item != NULL); + + /* fallback (few greedy match) not ambigous (has a value) */ + if (prevItem != NULL) { + item = prevItem; + parent = prevParent; + start = prevf; + } + +done: + + if (foundParent) + *foundParent = parent; + if (foundItem) + *foundItem = item; + return start; +} + +void +TclStrIdxTreeFree( + TclStrIdx *tree) +{ + while (tree != NULL) { + TclStrIdx *t; + Tcl_DecrRefCount(tree->key); + if (tree->childTree.firstPtr != NULL) { + TclStrIdxTreeFree(tree->childTree.firstPtr); + } + t = tree, tree = tree->nextPtr; + 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, + int lstc, + Tcl_Obj **lstv, + ClientData *values) +{ + Tcl_Obj **lwrv; + int i, ret = TCL_ERROR; + ClientData 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 + ) { + Tcl_SetObjRef(foundItem->key, lwrv[i]); + foundItem->length = lwrv[i]->length; + continue; + } + /* split tree (e. g. j->(jan,jun) + jul == j->(jan,ju->(jun,jul)) ) + * but don't split by fulfilled child of found item ( ii->iii->iiii ) */ + if (foundItem->length != (f - s)) { + /* first split found item (insert one between parent and found + new one) */ + item = (TclStrIdx *)ckalloc(sizeof(TclStrIdx)); + if (item == NULL) { + goto done; + } + Tcl_InitObjRef(item->key, foundItem->key); + item->length = f - s; + /* set value or mark as ambigous if not the same value of both */ + item->value = (foundItem->value == val) ? val : NULL; + /* insert group item between foundParent and foundItem */ + TclStrIdxTreeInsertBranch(foundParent, item, foundItem); + foundParent = &item->childTree; + } else { + /* the new item should be added as child of found item */ + foundParent = &foundItem->childTree; + } + } + } + /* append item at end of found parent */ + item = (TclStrIdx *)ckalloc(sizeof(TclStrIdx)); + if (item == NULL) { + goto done; + } + item->childTree.lastPtr = item->childTree.firstPtr = NULL; + Tcl_InitObjRef(item->key, lwrv[i]); + item->length = lwrv[i]->length; + item->value = val; + TclStrIdxTreeAppend(foundParent, item); + }; + + ret = TCL_OK; + +done: + + if (lwrv != NULL) { + for (i = 0; i < lstc; i++) { + Tcl_DecrRefCount(lwrv[i]); + } + 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_Obj* +TclStrIdxTreeNewObj() +{ + Tcl_Obj *objPtr = Tcl_NewObj(); + objPtr->internalRep.twoPtrValue.ptr1 = NULL; + objPtr->internalRep.twoPtrValue.ptr2 = NULL; + objPtr->typePtr = &StrIdxTreeObjType; + /* return tree root in internal representation */ + return objPtr; +} + +static void +StrIdxTreeObj_DupIntRepProc(Tcl_Obj *srcPtr, Tcl_Obj *copyPtr) +{ + /* follow links (smart pointers) */ + if ( srcPtr->internalRep.twoPtrValue.ptr1 != NULL + && srcPtr->internalRep.twoPtrValue.ptr2 == NULL + ) { + srcPtr = (Tcl_Obj*)srcPtr->internalRep.twoPtrValue.ptr1; + } + /* create smart pointer to it (ptr1 != NULL, ptr2 = NULL) */ + Tcl_InitObjRef(*((Tcl_Obj **)©Ptr->internalRep.twoPtrValue.ptr1), + srcPtr); + copyPtr->internalRep.twoPtrValue.ptr2 = NULL; + copyPtr->typePtr = &StrIdxTreeObjType; +} + +static void +StrIdxTreeObj_FreeIntRepProc(Tcl_Obj *objPtr) +{ + /* follow links (smart pointers) */ + if ( objPtr->internalRep.twoPtrValue.ptr1 != NULL + && objPtr->internalRep.twoPtrValue.ptr2 == NULL + ) { + /* is a link */ + Tcl_UnsetObjRef(*((Tcl_Obj **)&objPtr->internalRep.twoPtrValue.ptr1)); + } else { + /* is a tree */ + TclStrIdxTree *tree = (TclStrIdxTree*)&objPtr->internalRep.twoPtrValue.ptr1; + if (tree->firstPtr != NULL) { + TclStrIdxTreeFree(tree->firstPtr); + } + objPtr->internalRep.twoPtrValue.ptr1 = NULL; + objPtr->internalRep.twoPtrValue.ptr2 = NULL; + } + objPtr->typePtr = NULL; +}; + +static void +StrIdxTreeObj_UpdateStringProc(Tcl_Obj *objPtr) +{ + /* currently only dummy empty string possible */ + objPtr->length = 0; + objPtr->bytes = &tclEmptyString; +}; + +TclStrIdxTree * +TclStrIdxTreeGetFromObj(Tcl_Obj *objPtr) { + /* follow links (smart pointers) */ + if (objPtr->typePtr != &StrIdxTreeObjType) { + return NULL; + } + if ( objPtr->internalRep.twoPtrValue.ptr1 != NULL + && objPtr->internalRep.twoPtrValue.ptr2 == NULL + ) { + objPtr = (Tcl_Obj*)objPtr->internalRep.twoPtrValue.ptr1; + } + /* return tree root in internal representation */ + return (TclStrIdxTree*)&objPtr->internalRep.twoPtrValue.ptr1; +} + +/* + * Several debug primitives + */ +#if 0 +/* currently unused, debug resp. test purposes only */ + +void +TclStrIdxTreePrint( + Tcl_Interp *interp, + TclStrIdx *tree, + int offs) +{ + Tcl_Obj *obj[2]; + const char *s; + Tcl_InitObjRef(obj[0], Tcl_NewStringObj("::puts", -1)); + while (tree != NULL) { + s = TclGetString(tree->key) + offs; + Tcl_InitObjRef(obj[1], Tcl_ObjPrintf("%*s%.*s\t:%d", + offs, "", tree->length - offs, s, tree->value)); + Tcl_PutsObjCmd(NULL, interp, 2, obj); + Tcl_UnsetObjRef(obj[1]); + if (tree->childTree.firstPtr != NULL) { + TclStrIdxTreePrint(interp, tree->childTree.firstPtr, tree->length); + } + tree = tree->nextPtr; + } + Tcl_UnsetObjRef(obj[0]); +} + + +int +TclStrIdxTreeTestObjCmd( + ClientData clientData, Tcl_Interp *interp, + int objc, Tcl_Obj *const objv[]) +{ + const char *cs, *cin, *ret; + + static const char *const options[] = { + "index", "puts-index", "findequal", + NULL + }; + enum optionInd { + O_INDEX, O_PUTS_INDEX, O_FINDEQUAL + }; + int optionIndex; + + if (objc < 2) { + Tcl_WrongNumArgs(interp, 1, objv, ""); + return TCL_ERROR; + } + if (Tcl_GetIndexFromObj(interp, objv[1], options, + "option", 0, &optionIndex) != TCL_OK) { + Tcl_SetErrorCode(interp, "CLOCK", "badOption", + Tcl_GetString(objv[1]), NULL); + return TCL_ERROR; + } + switch (optionIndex) { + case O_FINDEQUAL: + if (objc < 4) { + Tcl_WrongNumArgs(interp, 1, objv, ""); + return TCL_ERROR; + } + cs = TclGetString(objv[2]); + cin = TclGetString(objv[3]); + ret = TclUtfFindEqual( + cs, cs + objv[1]->length, cin, cin + objv[2]->length); + Tcl_SetObjResult(interp, Tcl_NewIntObj(ret - cs)); + break; + case O_INDEX: + case O_PUTS_INDEX: + + if (1) { + Tcl_Obj **lstv; + int i, lstc; + TclStrIdxTree idxTree = {NULL, NULL}; + i = 1; + while (++i < objc) { + if (TclListObjGetElements(interp, objv[i], + &lstc, &lstv) != TCL_OK) { + return TCL_ERROR; + }; + TclStrIdxTreeBuildFromList(&idxTree, lstc, lstv, NULL); + } + if (optionIndex == O_PUTS_INDEX) { + TclStrIdxTreePrint(interp, idxTree.firstPtr, 0); + } + TclStrIdxTreeFree(idxTree.firstPtr); + } + break; + } + + return TCL_OK; +} + +#endif + +/* + * Local Variables: + * mode: c + * c-basic-offset: 4 + * fill-column: 78 + * End: + */ ADDED generic/tclStrIdxTree.h Index: generic/tclStrIdxTree.h ================================================================== --- /dev/null +++ generic/tclStrIdxTree.h @@ -0,0 +1,156 @@ +/* + * tclStrIdxTree.h -- + * + * Declarations of string index tries and other primitives currently + * back-ported from tclSE. + * + * Copyright (c) 2016 Serg G. Brester (aka sebres) + * + * See the file "license.terms" for information on usage and redistribution + * of this file, and for a DISCLAIMER OF ALL WARRANTIES. + */ + +#ifndef _TCLSTRIDXTREE_H +#define _TCLSTRIDXTREE_H + + +/* + * Main structures declarations of index tree and entry + */ + +typedef struct TclStrIdxTree { + struct TclStrIdx *firstPtr; + struct TclStrIdx *lastPtr; +} TclStrIdxTree; + +typedef struct TclStrIdx { + struct TclStrIdxTree childTree; + struct TclStrIdx *nextPtr; + struct TclStrIdx *prevPtr; + Tcl_Obj *key; + int length; + ClientData 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( + register const char *cs, /* UTF string to find in cin. */ + register const char *cse, /* End of cs */ + register const char *cin, /* UTF string will be browsed. */ + register const char *cine) /* End of cin */ +{ + register 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( + register const char *cs, /* UTF string to find in cin. */ + register const char *cse, /* End of cs */ + register const char *cin, /* UTF string will be browsed. */ + register const char *cine, /* End of cin */ + const char **cinfnd) /* Return position in cin */ +{ + register 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( + register const char *cs, /* UTF string (in anycase) to find in cin. */ + register const char *cse, /* End of cs */ + register const char *cin, /* UTF string (in lowercase) will be browsed. */ + register const char *cine, /* End of cin */ + const char **cinfnd) /* Return position in cin */ +{ + register const char *ret = cs; + Tcl_UniChar ch1, ch2; + do { + cs += TclUtfToUniChar(cs, &ch1); + cin += TclUtfToUniChar(cin, &ch2); + if (ch1 != ch2) { + ch1 = Tcl_UniCharToLower(ch1); + if (ch1 != ch2) break; + } + *cinfnd = cin; + } while ((ret = cs) < cse && cin < cine); + return ret; +} + +/* + * Primitives to safe set, reset and free references. + */ + +#define Tcl_UnsetObjRef(obj) \ + if (obj != NULL) { Tcl_DecrRefCount(obj); obj = NULL; } +#define Tcl_InitObjRef(obj, val) \ + obj = val; if (obj) { Tcl_IncrRefCount(obj); } +#define Tcl_SetObjRef(obj, val) \ +if (1) { \ + Tcl_Obj *nval = val; \ + if (obj != nval) { \ + Tcl_Obj *prev = obj; \ + Tcl_InitObjRef(obj, nval); \ + if (prev != NULL) { Tcl_DecrRefCount(prev); }; \ + } \ +} + +/* + * Prototypes of module functions. + */ + +MODULE_SCOPE const char* + TclStrIdxTreeSearch(TclStrIdxTree **foundParent, + TclStrIdx **foundItem, TclStrIdxTree *tree, + const char *start, const char *end); + +MODULE_SCOPE int TclStrIdxTreeBuildFromList(TclStrIdxTree *idxTree, + int lstc, Tcl_Obj **lstv, ClientData *values); + +MODULE_SCOPE Tcl_Obj* + TclStrIdxTreeNewObj(); + +MODULE_SCOPE TclStrIdxTree* + TclStrIdxTreeGetFromObj(Tcl_Obj *objPtr); + +#if 1 + +MODULE_SCOPE int TclStrIdxTreeTestObjCmd(ClientData, Tcl_Interp *, + int, Tcl_Obj *const objv[]); +#endif + +#endif /* _TCLSTRIDXTREE_H */ Index: library/clock.tcl ================================================================== --- library/clock.tcl +++ library/clock.tcl @@ -8,32 +8,25 @@ # # #---------------------------------------------------------------------- # # Copyright (c) 2004-2007 Kevin B. Kenny +# Copyright (c) 2015 by 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. # #---------------------------------------------------------------------- -# We must have message catalogs that support the root locale, and we need -# access to the Registry on Windows systems. - -uplevel \#0 { - package require msgcat 1.6 - if { $::tcl_platform(platform) eq {windows} } { - if { [catch { package require registry 1.1 }] } { - namespace eval ::tcl::clock [list variable NoRegistry {}] - } - } -} +# We must have message catalogs that support the root locale. + +package require msgcat 1.6 # Put the library directory into the namespace for the ensemble so that the # library code can find message catalogs and time zone definition files. namespace eval ::tcl::clock \ - [list variable LibDir [file dirname [info script]]] + [list variable LibDir [info library]] #---------------------------------------------------------------------- # # clock -- # @@ -56,13 +49,11 @@ namespace export seconds namespace export add # Import the message catalog commands that we use. - namespace import ::msgcat::mcload namespace import ::msgcat::mclocale - namespace import ::msgcat::mc namespace import ::msgcat::mcpackagelocale } #---------------------------------------------------------------------- @@ -221,11 +212,11 @@ # Russia ::msgcat::mcset ru GREGORIAN_CHANGE_DATE 2421639 - # Romania (Transylvania changed earlier - perhaps de_RO should show the + # Romania (Transylvania changed earler - perhaps de_RO should show the # earlier date?) ::msgcat::mcset ro GREGORIAN_CHANGE_DATE 2422063 # Greece @@ -285,10 +276,17 @@ # Day before Leap Day variable FEB_28 58 + # Default configuration + + configure -current-locale [mclocale] + #configure -default-locale C + #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 # time zone. @@ -308,55 +306,55 @@ variable WinZoneInfo [dict create {*}{ {-43200 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Pacific/Kwajalein {-39600 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Pacific/Midway {-36000 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Pacific/Honolulu - {-32400 0 3600 0 11 0 1 2 0 0 0 0 3 0 2 2 0 0 0} :America/Anchorage - {-28800 0 3600 0 11 0 1 2 0 0 0 0 3 0 2 2 0 0 0} :America/Los_Angeles - {-28800 0 3600 0 10 0 5 2 0 0 0 0 4 0 1 2 0 0 0} :America/Tijuana - {-25200 0 3600 0 11 0 1 2 0 0 0 0 3 0 2 2 0 0 0} :America/Denver - {-25200 0 3600 0 10 0 5 2 0 0 0 0 4 0 1 2 0 0 0} :America/Chihuahua + {-32400 0 3600 0 11 0 1 2 0 0 0 0 3 0 2 2 0 0 0} :America/Anchorage + {-28800 0 3600 0 11 0 1 2 0 0 0 0 3 0 2 2 0 0 0} :America/Los_Angeles + {-28800 0 3600 0 10 0 5 2 0 0 0 0 4 0 1 2 0 0 0} :America/Tijuana + {-25200 0 3600 0 11 0 1 2 0 0 0 0 3 0 2 2 0 0 0} :America/Denver + {-25200 0 3600 0 10 0 5 2 0 0 0 0 4 0 1 2 0 0 0} :America/Chihuahua {-25200 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :America/Phoenix {-21600 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :America/Regina {-21600 0 3600 0 11 0 1 2 0 0 0 0 3 0 2 2 0 0 0} :America/Chicago - {-21600 0 3600 0 10 0 5 2 0 0 0 0 4 0 1 2 0 0 0} :America/Mexico_City + {-21600 0 3600 0 10 0 5 2 0 0 0 0 4 0 1 2 0 0 0} :America/Mexico_City {-18000 0 3600 0 11 0 1 2 0 0 0 0 3 0 2 2 0 0 0} :America/New_York {-18000 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :America/Indianapolis {-14400 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :America/Caracas - {-14400 0 3600 0 3 6 2 23 59 59 999 0 10 6 2 23 59 59 999} + {-14400 0 3600 0 3 6 2 23 59 59 999 0 10 6 2 23 59 59 999} :America/Santiago - {-14400 0 3600 0 2 0 5 2 0 0 0 0 11 0 1 2 0 0 0} :America/Manaus - {-14400 0 3600 0 11 0 1 2 0 0 0 0 3 0 2 2 0 0 0} :America/Halifax + {-14400 0 3600 0 2 0 5 2 0 0 0 0 11 0 1 2 0 0 0} :America/Manaus + {-14400 0 3600 0 11 0 1 2 0 0 0 0 3 0 2 2 0 0 0} :America/Halifax {-12600 0 3600 0 10 0 5 2 0 0 0 0 4 0 1 2 0 0 0} :America/St_Johns {-10800 0 3600 0 2 0 2 2 0 0 0 0 10 0 3 2 0 0 0} :America/Sao_Paulo {-10800 0 3600 0 10 0 5 2 0 0 0 0 4 0 1 2 0 0 0} :America/Godthab {-10800 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :America/Buenos_Aires - {-10800 0 3600 0 2 0 5 2 0 0 0 0 11 0 1 2 0 0 0} :America/Bahia - {-10800 0 3600 0 3 0 2 2 0 0 0 0 10 0 1 2 0 0 0} :America/Montevideo + {-10800 0 3600 0 2 0 5 2 0 0 0 0 11 0 1 2 0 0 0} :America/Bahia + {-10800 0 3600 0 3 0 2 2 0 0 0 0 10 0 1 2 0 0 0} :America/Montevideo {-7200 0 3600 0 9 0 5 2 0 0 0 0 3 0 5 2 0 0 0} :America/Noronha {-3600 0 3600 0 10 0 5 3 0 0 0 0 3 0 5 2 0 0 0} :Atlantic/Azores {-3600 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Atlantic/Cape_Verde {0 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :UTC {0 0 3600 0 10 0 5 2 0 0 0 0 3 0 5 1 0 0 0} :Europe/London {3600 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Africa/Kinshasa {3600 0 3600 0 10 0 5 3 0 0 0 0 3 0 5 2 0 0 0} :CET - {7200 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Africa/Harare - {7200 0 3600 0 9 4 5 23 59 59 0 0 4 4 5 23 59 59 0} + {7200 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Africa/Harare + {7200 0 3600 0 9 4 5 23 59 59 0 0 4 4 5 23 59 59 0} :Africa/Cairo {7200 0 3600 0 10 0 5 4 0 0 0 0 3 0 5 3 0 0 0} :Europe/Helsinki - {7200 0 3600 0 9 0 3 2 0 0 0 0 3 5 5 2 0 0 0} :Asia/Jerusalem + {7200 0 3600 0 9 0 3 2 0 0 0 0 3 5 5 2 0 0 0} :Asia/Jerusalem {7200 0 3600 0 9 0 5 1 0 0 0 0 3 0 5 0 0 0 0} :Europe/Bucharest {7200 0 3600 0 10 0 5 3 0 0 0 0 3 0 5 2 0 0 0} :Europe/Athens - {7200 0 3600 0 9 5 5 1 0 0 0 0 3 4 5 0 0 0 0} :Asia/Amman - {7200 0 3600 0 10 6 5 23 59 59 999 0 3 0 5 0 0 0 0} + {7200 0 3600 0 9 5 5 1 0 0 0 0 3 4 5 0 0 0 0} :Asia/Amman + {7200 0 3600 0 10 6 5 23 59 59 999 0 3 0 5 0 0 0 0} :Asia/Beirut - {7200 0 -3600 0 4 0 1 2 0 0 0 0 9 0 1 2 0 0 0} :Africa/Windhoek + {7200 0 -3600 0 4 0 1 2 0 0 0 0 9 0 1 2 0 0 0} :Africa/Windhoek {10800 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Asia/Riyadh {10800 0 3600 0 10 0 1 4 0 0 0 0 4 0 1 3 0 0 0} :Asia/Baghdad {10800 0 3600 0 10 0 5 3 0 0 0 0 3 0 5 2 0 0 0} :Europe/Moscow {12600 0 3600 0 9 2 4 2 0 0 0 0 3 0 1 2 0 0 0} :Asia/Tehran - {14400 0 3600 0 10 0 5 5 0 0 0 0 3 0 5 4 0 0 0} :Asia/Baku + {14400 0 3600 0 10 0 5 5 0 0 0 0 3 0 5 4 0 0 0} :Asia/Baku {14400 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Asia/Muscat {14400 0 3600 0 10 0 5 3 0 0 0 0 3 0 5 2 0 0 0} :Asia/Tbilisi {16200 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Asia/Kabul {18000 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Asia/Karachi {18000 0 3600 0 10 0 5 3 0 0 0 0 3 0 5 2 0 0 0} :Asia/Yekaterinburg @@ -381,156 +379,10 @@ {43200 0 3600 0 3 0 3 3 0 0 0 0 10 0 1 2 0 0 0} :Pacific/Auckland {43200 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Pacific/Fiji {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 \ gmt +0000 \ ut +0000 \ @@ -552,10 +404,12 @@ mdt -0600 \ pst -0800 \ pdt -0700 \ yst -0900 \ ydt -0800 \ + akst -0900 \ + akdt -0800 \ hst -1000 \ hdt -0900 \ cat -1000 \ ahst -1000 \ nt -1100 \ @@ -582,12 +436,12 @@ jt +0730 \ cct +0800 \ jst +0900 \ kst +0900 \ cast +0930 \ - jdt +1000 \ - kdt +1000 \ + jdt +1000 \ + kdt +1000 \ cadt +1030 \ east +1000 \ eadt +1030 \ gst +1000 \ nzt +1200 \ @@ -621,1673 +475,163 @@ z +0000 \ ] # 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. variable TZData; # Array whose keys are time zone names # and whose values are lists of quads # 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] - -} - -#---------------------------------------------------------------------- -# -# ParseClockFormatFormat -- -# -# Builds and caches a procedure that formats a time value. -# -# Parameters: -# format -- Format string to use -# locale -- Locale in which the format string is to be interpreted -# -# 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. -# -#---------------------------------------------------------------------- - -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?\"" - } - - # 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 { ![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] - } - - # 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 - } -} - -#---------------------------------------------------------------------- -# -# FreeScan -- -# -# 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. -# -# 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 - } 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 {} - } - } - } - - # 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 - } - append re \) - dict set LocaleNumeralCache $l [list $re $d] - } - return [dict get $LocaleNumeralCache $l] -} - - - -#---------------------------------------------------------------------- -# -# UniquePrefixRegexp -- -# -# Composes a regexp that performs unique-prefix matching. The RE -# matches one of a supplied set of strings, or any unique prefix -# thereof. -# -# 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'. -# -# Side effects: -# None. -# -#---------------------------------------------------------------------- - -proc ::tcl::clock::MakeUniquePrefixRegexp { successors - uniquePrefixMapping - prefixString } { - - # 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 {} - } - - # 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. - - 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 | - } - - # 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. - - if { [dict exists $uniquePrefixMapping $prefixString] } { - append re ")?" - } elseif { [llength $schars] > 1 } { - append re ")" - } - - return $re -} - -#---------------------------------------------------------------------- -# -# MakeParseCodeFromFields -- -# -# Composes Tcl code to extract the Julian Day Number from a dictionary -# containing date fields. -# -# 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 -# -# Results: -# Returns a burst of code that extracts the day number from the given -# date. -# -# Side effects: -# None. -# -#---------------------------------------------------------------------- - -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 - } - - return $currCodeBurst + +# mcget -- +# +# Return the merged translation catalog for the ::tcl::clock namespace +# Searching of catalog is similar to "msgcat::mc". +# +# Contrary to "msgcat::mc" may additionally load a package catalog +# on demand. +# +# Arguments: +# loc The locale used for translation. +# +# Results: +# Returns the dictionary object as whole catalog of the package/locale. +# +proc ::tcl::clock::mcget {loc} { + variable mcMergedCat + switch -- $loc system { + set loc [GetSystemLocale] + } current { + set loc [mclocale] + } + if {$loc ne {}} { + set loc [string tolower $loc] + } + + # try to retrieve now if already available: + if {[dict exists $mcMergedCat $loc]} { + return [dict get $mcMergedCat $loc] + } + + # 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::GetPreferences $loc] + dict set $mcLocales $loc $loclist + # restore: + if {$prevloc ne $loc} { + mcpackagelocale set $prevloc + } + } + # get whole catalog: + mcMerge $loclist +} + +# mcMerge -- +# +# Merge message catalog dictionaries to one dictionary. +# +# Arguments: +# locales List of locales to merge. +# +# Results: +# 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 { + # be sure a duplicate is created, don't overwrite {} (common) locale: + set mrgcat [dict merge $mrgcat [dict create L $loc]] + } + } 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] + } + } + dict set mcMergedCat $loc $mrgcat + # return smart reference (shared dict as object with exact one ref-counter) + return $mrgcat +} + +#---------------------------------------------------------------------- +# +# GetSystemLocale -- +# +# Determines the system locale, which corresponds to "system" +# keyword for locale parameter of 'clock' command. +# +# Parameters: +# None. +# +# Results: +# Returns the system locale. +# +# Side effects: +# None +# +#---------------------------------------------------------------------- + +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 + + return [mclocale] + } + + # 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 + } + + return $locale } #---------------------------------------------------------------------- # # EnterLocale -- @@ -2299,43 +643,51 @@ # # Results: # Returns the locale that was previously current. # # Side effects: -# Does [mclocale]. If necessary, loads the designated locale's files. +# Does [mclocale]. If necessary, loades the designated locale's files. # #---------------------------------------------------------------------- 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 - } - } - } - if { $locale eq {current}} { + switch -- $locale system { + set locale [GetSystemLocale] + } current { set locale [mclocale] } - # Eventually load the locale + # Select the locale, eventually load it mcpackagelocale set $locale + return $locale +} + +#---------------------------------------------------------------------- +# +# _hasRegistry -- +# +# Helper that checks whether registry module is available (Windows only) +# and loads it on demand. +# +#---------------------------------------------------------------------- +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 + } + } + proc ::tcl::clock::_hasRegistry {} [list return $res] + return $res } #---------------------------------------------------------------------- # # LoadWindowsDateTimeFormats -- @@ -2359,12 +711,11 @@ #---------------------------------------------------------------------- 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" \ sShortDate } string] } { @@ -2380,12 +731,12 @@ MMM %b MM %m M %N yyyy %Y yy %y - y %y - gg {} + y %y + gg {} } $unquoted] if { $quoted eq {} } { set quote ' } else { set quote $quoted @@ -2410,12 +761,12 @@ MMM %b MM %m M %N yyyy %Y yy %y - y %y - gg {} + y %y + gg {} } $unquoted] if { $quoted eq {} } { set quote ' } else { set quote $quoted @@ -2471,10 +822,12 @@ # # Parameters: # 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 # substituted out. # @@ -2481,489 +834,55 @@ # Side effects: # None. # #---------------------------------------------------------------------- -proc ::tcl::clock::LocalizeFormat { locale format } { - - # 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 - - } 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 integervalueTooLarge] \ - "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] - }] +proc ::tcl::clock::LocalizeFormat { locale format mcd } { + variable LocFmtMap + + # get map list cached or build it: + if {[dict exists $LocFmtMap $locale]} { + set mlst [dict get $LocFmtMap $locale] + } else { + # 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 } #---------------------------------------------------------------------- # # GetSystemTimeZone -- @@ -2976,79 +895,50 @@ # # Results: # 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 sustem 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 {}} { set timezone $result } 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 [configure -system-tz] + if { $timezone ne "" } { + return $timezone + } + if { $::tcl_platform(platform) eq {windows} } { set timezone [GuessWindowsTimeZone] } elseif { [file exists /etc/localtime] - && ![catch {ReadZoneinfoFile \ - Tcl/Localtime /etc/localtime}] } { + && ![catch {ReadZoneinfoFile \ + Tcl/Localtime /etc/localtime}] } { set timezone :Tcl/Localtime } 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 - } -} - -#---------------------------------------------------------------------- -# -# 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? -# -#---------------------------------------------------------------------- - -proc ::tcl::clock::ConvertLegacyTimeZone { tzname } { - variable LegacyTimeZone - - 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] + catch {set timezone [SetupTimeZone $timezone]} + } + + if { [dict exists $TimeZoneBad $timezone] } { + set timezone :localtime + } + + # tell backend - current system timezone: + configure -system-tz $timezone + + return $timezone } #---------------------------------------------------------------------- # # SetupTimeZone -- @@ -3064,19 +954,23 @@ # the lookup table for local<->UTC conversion. Returns an error if the # time zone cannot be parsed. # #---------------------------------------------------------------------- -proc ::tcl::clock::SetupTimeZone { timezone } { +proc ::tcl::clock::SetupTimeZone { timezone {alias {}} } { variable TZData if {! [info exists TZData($timezone)] } { + + variable TimeZoneBad + if { [dict exists $TimeZoneBad $timezone] } { + return -code error \ + -errorcode [list CLOCK badTimeZone $timezone] \ + "time zone \"$timezone\" not found" + } variable MINWIDE - if { $timezone eq {:localtime} } { - # Nothing to do, we'll convert using the localtime function - - } elseif { + if { [regexp {^([-+])(\d\d)(?::?(\d\d)(?::?(\d\d))?)?} $timezone \ -> s hh mm ss] } then { # Make a fixed offset @@ -3105,10 +999,11 @@ LoadTimeZoneFile [string range $timezone 1 end] }] && [catch { 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" } } elseif { ![catch {ParsePosixTimeZone $timezone} tzfields] } { @@ -3116,29 +1011,47 @@ if { [catch {ProcessPosixTimeZone $tzfields} data opts] } { 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 [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: + configure -setup-tz $timezone } #---------------------------------------------------------------------- # # GuessWindowsTimeZone -- @@ -3164,14 +1077,13 @@ # #---------------------------------------------------------------------- proc ::tcl::clock::GuessWindowsTimeZone {} { variable WinZoneInfo - variable NoRegistry variable TimeZoneBad - if { [info exists NoRegistry] } { + if { ![_hasRegistry] } { return :localtime } # Dredge time zone information out of the registry @@ -3205,16 +1117,16 @@ # (e.g. starpack) where tzdata is incomplete. (Bug 1237907) 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 \ stdHour stdMinute stdSecond stdMillisec \ dstYear dstMonth dstDayOfWeek dstDayOfMonth \ @@ -3402,11 +1314,11 @@ fconfigure $f -translation binary set d [read $f] close $f # The file begins with a magic number, sixteen reserved bytes, and then - # six 4-byte integers giving counts of fields in the file. + # six 4-byte integers giving counts of fileds in the file. binary scan $d a4a1x15IIIIII \ magic version nIsGMT nIsStd nLeap nTime nType nChar set seek 44 set ilen 4 @@ -3609,47 +1521,47 @@ ([[:digit:]]{1,2}) (?: # 4 - Standard time zone offset, minutes : ([[:digit:]]{1,2}) (?: - # 5 - Standard time zone offset, seconds + # 5 - Standard time zone offset, seconds : ([[:digit:]]{1,2} ) )? )? (?: # 6 - DST time zone name ([[:alpha:]]+ | <[-+[:alnum:]]+>) (?: - (?: + (?: # 7 - DST time zone offset, signum ([-+]?) # 8 - DST time zone offset, hours ([[:digit:]]{1,2}) (?: # 9 - DST time zone offset, minutes : ([[:digit:]]{1,2}) (?: - # 10 - DST time zone offset, seconds + # 10 - DST time zone offset, seconds : ([[:digit:]]{1,2}) )? )? )? - (?: + (?: , (?: # 11 - Optional J in n and Jn form 12 - Day of year - ( J ? ) ( [[:digit:]]+ ) - | M + ( J ? ) ( [[:digit:]]+ ) + | M # 13 - Month number 14 - Week of month 15 - Day of week ( [[:digit:]] + ) [.] ( [[:digit:]] + ) [.] ( [[:digit:]] + ) ) (?: # 16 - Start time of DST - hours / ( [[:digit:]]{1,2} ) - (?: + (?: # 17 - Start time of DST - minutes : ( [[:digit:]]{1,2} ) (?: # 18 - Start time of DST - seconds : ( [[:digit:]]{1,2} ) @@ -3657,32 +1569,32 @@ )? )? , (?: # 19 - Optional J in n and Jn form 20 - Day of year - ( J ? ) ( [[:digit:]]+ ) - | M + ( J ? ) ( [[:digit:]]+ ) + | M # 21 - Month number 22 - Week of month 23 - Day of week ( [[:digit:]] + ) [.] ( [[:digit:]] + ) [.] ( [[:digit:]] + ) ) (?: # 24 - End time of DST - hours / ( [[:digit:]]{1,2} ) - (?: + (?: # 25 - End time of DST - minutes : ( [[:digit:]]{1,2} ) (?: # 26 - End time of DST - seconds : ( [[:digit:]]{1,2} ) )? )? )? - )? + )? )? - )? + )? $ } $tz -> x(stdName) x(stdSignum) x(stdHours) x(stdMinutes) x(stdSeconds) \ x(dstName) x(dstSignum) x(dstHours) x(dstMinutes) x(dstSeconds) \ x(startJ) x(startDayOfYear) \ x(startMonth) x(startWeekOfMonth) x(startDayOfWeek) \ @@ -3881,18 +1793,18 @@ variable FEB_28 # 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 } dict set date dayOfYear $doy set date [GetJulianDayFromEraYearDay $date[set date {}] 2361222] @@ -3935,47 +1847,10 @@ } set tod [expr { ( $h * 60 + $m ) * 60 + $s }] return [expr { $seconds + $tod }] } -#---------------------------------------------------------------------- -# -# 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 @@ -4151,337 +2026,10 @@ return [expr { $j - ( $j - $k ) % 7 }] } #---------------------------------------------------------------------- # -# 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. # Clears the buffered parse functions of the current locale. # @@ -4495,24 +2043,12 @@ # Buffered parse functions are cleared. # #---------------------------------------------------------------------- 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 {} + + configure -current-locale [lindex $args 0] } #---------------------------------------------------------------------- # # ClearCaches -- @@ -4529,23 +2065,19 @@ # Caches are cleared. # #---------------------------------------------------------------------- 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 {} - } - - catch {unset FormatProc} - set LocaleNumeralCache {} - catch {unset CachedSystemTimeZone} + # tell backend - should invalidate: + configure -clear + + # clear msgcat cache: + set mcMergedCat [dict create] + + set LocFmtMap {} set TimeZoneBad {} InitTZData } Index: library/init.tcl ================================================================== --- library/init.tcl +++ library/init.tcl @@ -47,10 +47,11 @@ set auto_path $env(TCLLIBPATH) } else { set auto_path "" } } + namespace eval tcl { if {![interp issafe]} { variable Dir foreach Dir [list $::tcl_library [file dirname $::tcl_library]] { if {$Dir ni $::auto_path} { @@ -158,10 +159,21 @@ if {[interp issafe]} { package unknown {::tcl::tm::UnknownHandler ::tclPkgUnknown} } else { + # Default known auto_index (avoid loading auto index implicit after interp create): + + array set ::auto_index { + ::tcl::tm::UnknownHandler {source [info library]/tm.tcl} + ::tclPkgUnknown {source [info library]/package.tcl} + ::history {source [info library]/history.tcl} + } + + # The newest possibility to load whole namespace: + array set ::auto_index_ns {} + # Set up search for Tcl Modules (TIP #189). # and setup platform specific unknown package handlers if {$tcl_platform(os) eq "Darwin" && $tcl_platform(platform) eq "unix"} { package unknown {::tcl::tm::UnknownHandler \ @@ -170,26 +182,25 @@ package unknown {::tcl::tm::UnknownHandler ::tclPkgUnknown} } # 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 -encoding utf-8 [file join $TclLibDir clock.tcl] - return [uplevel 1 [info level 0]] - } - } - - rename ::tcl::initClock {} - } - ::tcl::initClock + proc clock args { + set cmdmap [dict create] + foreach cmd {add clicks format microseconds milliseconds scan seconds configure} { + dict set cmdmap $cmd ::tcl::clock::$cmd + } + namespace inscope ::tcl::clock [list namespace ensemble create -command \ + [uplevel 1 [list ::namespace origin [::lindex [info level 0] 0]]] \ + -map $cmdmap] + + uplevel 1 [info level 0] + } + # Auto-loading stubs for 'clock.tcl' + set ::auto_index_ns(::tcl::clock) {::namespace inscope ::tcl::clock { + ::source -encoding utf-8 [::file join [info library] clock.tcl] + }} } # Conditionalize for presence of exec. if {[namespace which -command exec] eq ""} { @@ -419,22 +430,26 @@ # namespace (optional) The namespace where the command is being used - must be # a canonical namespace as returned [namespace current] # for instance. If not given, namespace current is used. proc auto_load {cmd {namespace {}}} { - global auto_index auto_path + global auto_index auto_index_ns auto_path + # qualify names: if {$namespace eq ""} { set namespace [uplevel 1 [list ::namespace current]] } set nameList [auto_qualify $cmd $namespace] # workaround non canonical auto_index entries that might be around # from older auto_mkindex versions - lappend nameList $cmd - foreach name $nameList { + if {$cmd ni $nameList} {lappend nameList $cmd} + + # try to load (and create sub-cmd handler "_sub_load_cmd" for further usage): + foreach name $nameList [set _sub_load_cmd { + # via auto_index: if {[info exists auto_index($name)]} { - namespace eval :: $auto_index($name) + namespace inscope :: $auto_index($name) # There's a couple of ways to look for a command of a given # name. One is to use # info commands $name # Unfortunately, if the name has glob-magic chars in it like * # or [], it may not match. For our purposes here, a better @@ -442,26 +457,35 @@ # namespace which -command $name if {[namespace which -command $name] ne ""} { return 1 } } - } - if {![info exists auto_path]} { - return 0 - } - - if {![auto_load_index]} { - return 0 - } - foreach name $nameList { - if {[info exists auto_index($name)]} { - namespace eval :: $auto_index($name) + # via auto_index_ns - resolver for the whole namespace loaders + if {[set ns [::namespace qualifiers $name]] ni {"" "::"} && + [info exists auto_index_ns($ns)] + } { + # remove handler before loading (prevents several self-recursion cases): + set ldr $auto_index_ns($ns); unset auto_index_ns($ns) + namespace inscope :: $ldr + # if got it: if {[namespace which -command $name] ne ""} { return 1 } } + }] + + # load auto_index if possible: + if {![info exists auto_path]} { + return 0 + } + if {![auto_load_index]} { + return 0 } + + # try again (something new could be loaded): + foreach name $nameList $_sub_load_cmd + return 0 } # auto_load_index -- # Loads the contents of tclIndex files on the auto_path directory @@ -608,11 +632,11 @@ foreach pattern $patternList { foreach name [array names auto_index $pattern] { if {([namespace which -command $name] eq "") && ([namespace qualifiers $pattern] eq [namespace qualifiers $name])} { - namespace eval :: $auto_index($name) + namespace inscope :: $auto_index($name) } } } } Index: tests-perf/clock.perf.tcl ================================================================== --- tests-perf/clock.perf.tcl +++ tests-perf/clock.perf.tcl @@ -353,15 +353,21 @@ # Bad zone {catch {clock scan "1 day" -timezone BAD_ZONE -locale en}} # 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}} } } proc test-ensemble-perf {{reptime 1000}} { _test_run $reptime { ADDED tests/clock-ivm.test Index: tests/clock-ivm.test ================================================================== --- /dev/null +++ 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. + +clock configure -valid [expr {![clock configure -valid]}] +source [file join [file dirname [info script]] clock.test] Index: tests/clock.test ================================================================== --- tests/clock.test +++ tests/clock.test @@ -5,10 +5,11 @@ # This file contains a collection of tests for one or more of the Tcl # built-in commands. Sourcing this file into Tcl runs the tests and # generates output for errors. No output means no errors were found. # # Copyright (c) 2004 by Kevin B. Kenny. All rights reserved. +# Copyright (c) 2015 by 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. if {"::tcltest" ni [namespace children]} { @@ -17,13 +18,12 @@ } if {[testConstraint win]} { if {[catch { ::tcltest::loadTestedCommands - package require registry }]} { - namespace eval ::tcl::clock {variable NoRegistry {}} + # nothing to be done (registry loaded on demand) } } package require msgcat 1.4 @@ -30,16 +30,34 @@ testConstraint detroit \ [expr {![catch {clock format 0 -timezone :America/Detroit -format %z}]}] testConstraint y2038 \ [expr {[clock format 2158894800 -format %z -timezone :America/Detroit] eq {-0400}}] +# Test with both validity modes - validate on / off: + +set valid_mode [clock configure -valid] + +# Wrapper to show validity mode in the test-case name (for possible errors): +rename test __test +proc test {args} { + variable valid_mode + lset args 0 [lindex $args 0].vm:$valid_mode + uplevel [linsert $args [set args 0] __test] +} + +puts [outputChannel] " Validity default mode: [expr {$valid_mode ? "on": "off"}]" +testConstraint valid_off [expr {![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 # # clock-2 # formatting of year, month and day of month @@ -252,15 +270,52 @@ return -code error "test case attempts to read unknown registry entry $path $key" } return [dict get $reg $path $key] } +# Base test cases: + +test clock-0.1 "initial: auto-loading of ensemble and stubs on demand" { + set i [interp create]; # because clock can be used somewhere, test it in new interp: + + set ret [$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 ""}] + }] + interp delete $i + set ret +} {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 "::tcl::clock::format $syntax"} {CLOCK wrongNumArgs}}] test clock-1.1 "clock format - bad time" { list [catch {clock format foo} msg] $msg } {1 {expected integer but got "foo"}} @@ -270,17 +325,18 @@ 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 } {1 {time zone ":NOWHERE" not found} {CLOCK badTimeZone :NOWHERE}} @@ -290,10 +346,20 @@ test clock-1.7 "clock format - option abbreviations" { clock format 0 -g true -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 # Formats tested: %b %B %c %Ec %C %EC %d %Od %e %Oe %h %j %J %m %Om %N %x %Ex %y %Oy %Y %EY @@ -15293,10 +15359,88 @@ clock format 86399 \ -format {%H %OH %I %OI %k %Ok %l %Ol %M %OM %p %P %r %R %S %OS %T %X %EX %+} \ -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 # Test formatting of Daylight Saving Time @@ -18534,20 +18678,210 @@ test clock-6.8 {input of seconds} { clock scan {9223372036854775807} -format %s -gmt true } 9223372036854775807 test clock-6.9 {input of seconds - overflow} { - list [catch {clock scan -9223372036854775809 -format %s -gmt true} result] $result -} {1 {integer value too large to represent}} + list [catch {clock scan -9223372036854775809 -format %s -gmt true} result] $result $::errorCode +} {1 {integer value too large to represent} {CLOCK dateTooLarge}} test clock-6.10 {input of seconds - overflow} { - list [catch {clock scan 9223372036854775808 -format %s -gmt true} result] $result -} {1 {integer value too large to represent}} + list [catch {clock scan 9223372036854775808 -format %s -gmt true} result] $result $::errorCode +} {1 {integer value too large to represent} {CLOCK dateTooLarge}} 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 @@ -18597,10 +18931,95 @@ } {86400 0 86400 0} test clock-7.9 {Julian Day, two values} { clock scan {2440588 2440589} -format {%J %J} -gmt true } 86400 +test clock-7.10 {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 {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} test clock-7.10 {Julian Day, negative amount} { # Note: %J does not accept negative input; # add negative amounts to Julian day 0 instead set s0 [clock scan 0 -format %J -gmt true] @@ -21022,13 +21441,26 @@ 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 test clock-10.1 {parse ccyyddd} { clock scan {1970 001} -format {%Y %j} -locale en_US_roman -gmt 1 @@ -21065,84 +21497,91 @@ [clock scan {2000001 2440588} -format {%Y%j %J} -gmt true] } {0 0} # 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 # Test parsing of ccyyWwwd @@ -21435,15 +21874,15 @@ test clock-12.96 {parse ccyyWwwd} { clock scan {2002 W01 i} -format {%G W%V %Ow} -locale en_US_roman -gmt 1 } 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} test clock-13.3 {invalid weekday} { catch {clock scan 2000W018 -format %GW%V%u -gmt true} result @@ -23775,11 +24214,11 @@ test clock-15.2 {yymmdd precedence below julian day} { list [clock scan {2440588 000101} -format {%J %y%m%d} -gmt true] \ [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} # Test parsing of yyddd @@ -23815,11 +24254,11 @@ } {0 0} 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} # BEGIN testcases17 @@ -24116,23 +24555,23 @@ } 1009756800 # END testcases17 # 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} 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} # BEGIN testcases19 @@ -25940,46 +26379,52 @@ test clock-26.48 {parse naked day of week} { clock scan ? -format %Ow -locale en_US_roman -gmt 1 -base 1009411200 } 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 } 1234483200 @@ -34985,12 +35430,39 @@ test clock-29.1800 {time parsing} { clock scan {2440588 xi:lix:lix pm} \ -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] clock format $f -format %Y-%m-%d -timezone :UTC } {2001-01-01} @@ -35231,10 +35703,99 @@ -timezone EST05:00EDT04:00,M4.1.0/02:00,M10.5.0/02:00] set f1 [clock add $t 3600 seconds -timezone EST05:00EDT04:00,M4.1.0/02:00,M10.5.0/02:00] 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 \ -setup { namespace eval ::tcl::clock { @@ -35600,13 +36161,14 @@ } expr { $t2 / 1000 == $t3 } } {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 -match glob -result {unable to convert date-time string "bad-string"*} @@ -35636,48 +36198,211 @@ set time [clock scan "Oct 23,1992 15:00" -gmt true] clock format $time -format {%b %d,%Y %H:%M GMT} -gmt true } {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] clock format $time -format {%b %d,%Y %H:%M GMT} -gmt true } {Jan 01,1971 00:00 GMT} 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"] + 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"] + 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] + 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] + 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"] + 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"] + 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] + 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] + 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 + # CLOCK SCAN REAL TESTS # We use 5am PST, 31-12-1999 as the base for these scans because irrespective # of your local timezone it should always give us times on December 31, 1999 set 5amPST 946645200 @@ -35768,10 +36493,31 @@ } "Jan 13, 2000" 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} { set res {} foreach i {91 92 93 94 95 96} { @@ -35854,10 +36600,17 @@ test clock-34.52 {more than one ordinal month} {*}{ -body {clock scan {next January next March}} -returnCodes error -result {unable to convert date-time string "next January next March": more than one ordinal month in string} } + +test clock-34.53.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.53 {clock scan, ISO 8601 point in time format} { set time [clock scan "19921023T00:00:00"] clock format $time -format {%b %d, %Y %H:%M:%S} } "Oct 23, 1992 00:00:00" test clock-34.54 {clock scan, ISO 8601 point in time format} { @@ -35918,10 +36671,92 @@ } {Jan 24,1970 21:59:00 GMT} test clock-34.68 {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.53.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.53.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.53.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.54.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.54.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} concat {} @@ -35949,17 +36784,37 @@ clock format [clock scan "next may" -base [clock scan "june 1, 2000"]] \ -format %m.%Y } "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 { if { [info exists env(TZ)] } { set oldTZ $env(TZ) @@ -36009,10 +36864,56 @@ unset oldTclTZ } } \ -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 } {19:00:00} @@ -36080,34 +36981,314 @@ } else { unset env(TZ) } } \ -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} + +# 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... + list [catch {clock scan "13:00 am$relstr" -valid 1 -gmt 1} msg] $msg \ + [catch {clock scan "13:00 pm$relstr" -valid 1 -gmt 1} msg] $msg + } -result {1 {unable to convert input string: invalid time (hour)} 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... + list [catch {clock scan "23:70$relstr" -valid 1 -gmt 1} msg] $msg \ + [catch {clock scan "11:80 pm$relstr" -valid 1 -gmt 1} msg] $msg + } -result {1 {unable to convert input string: invalid time (minutes)} 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... + list [catch {clock scan "23:00:70$relstr" -valid 1 -gmt 1} msg] $msg \ + [catch {clock scan "11:00:80 pm$relstr" -valid 1 -gmt 1} msg] $msg + } -result {1 {unable to convert input string: invalid time} 1 {unable to convert input string: invalid time}} +test clock-46.13$idx {freescan: validation rules: invalid day} \ + -body { + list [catch {clock scan "29 Feb 2017$relstr" -valid 1 -gmt 1} msg] $msg \ + [catch {clock scan "30 Feb 2016$relstr" -valid 1 -gmt 1} msg] $msg + } -result {1 {unable to convert input string: invalid day} 1 {unable to convert input string: invalid day}} +test clock-46.14$idx {freescan: validation rules: invalid day} \ + -body { + list [catch {clock scan "0 Feb 2017$relstr" -valid 1 -gmt 1} msg] $msg \ + [catch {clock scan "00 Feb 2017$relstr" -valid 1 -gmt 1} msg] $msg + } -result {1 {unable to convert input string: invalid day} 1 {unable to convert input string: invalid day}} +test clock-46.15$idx {freescan: validation rules: invalid month} \ + -body { + list [catch {clock scan "13/13/2017$relstr" -valid 1 -gmt 1} msg] $msg \ + [catch {clock scan "00/00/2017$relstr" -valid 1 -gmt 1} msg] $msg + } -result {1 {unable to convert input string: invalid month} 1 {unable to convert input string: invalid month}} +test clock-46.16$idx {freescan: validation rules: invalid day of week} \ + -body { + list [catch {clock scan "Sat Jan 01 00:00:00 1970$relstr" -valid 1 -gmt 1} msg] $msg \ + [catch {clock scan "Thu Jan 03 00:00:00 1970$relstr" -valid 1 -gmt 1} msg] $msg + } -result {1 {unable to convert input string: invalid day of week} 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 [clock configure -min-year] -max-year [clock configure -max-year] \ + -year-century [clock configure -year-century] -century-switch [clock configure -century-switch]] + clock configure -min-year 2000 -max-year 2100 -year-century 2000 -century-switch 38 + } -body { + list [catch {clock scan "70-01-01$relstr" -valid 1 -gmt 1} msg] $msg \ + [catch {clock scan "1870-01-01$relstr" -valid 1 -gmt 1} msg] $msg \ + [catch {clock scan "9570-01-01$relstr" -valid 1 -gmt 1} msg] $msg \ + } -result [lrepeat 3 1 {unable to convert input string: invalid year}] -cleanup { + clock configure {*}$orgcfg + unset -nocomplain orgcfg + } + +}; # foreach +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 + +test clock-46.20 {scan: validation rules: invalid time} \ + -body { + # 13:00 am/pm are invalid input strings... + list [catch {clock scan "13:00 am" -format "%H:%M %p" -valid 1 -gmt 1} msg] $msg \ + [catch {clock scan "13:00 pm" -format "%H:%M %p" -valid 1 -gmt 1} msg] $msg + } -result {1 {unable to convert input string: invalid time (hour)} 1 {unable to convert input string: invalid time (hour)}} +test clock-46.21 {scan: validation rules: invalid time} \ + -body { + # invalid minutes in input strings... + list [catch {clock scan "23:70" -format "%H:%M" -valid 1 -gmt 1} msg] $msg \ + [catch {clock scan "11:80 pm" -format "%H:%M %p" -valid 1 -gmt 1} msg] $msg + } -result {1 {unable to convert input string: invalid time (minutes)} 1 {unable to convert input string: invalid time (minutes)}} +test clock-46.22 {scan: validation rules: invalid time} \ + -body { + # invalid seconds in input strings... + list [catch {clock scan "23:00:70" -format "%H:%M:%S" -valid 1 -gmt 1} msg] $msg \ + [catch {clock scan "11:00:80 pm" -format "%H:%M:%S %p" -valid 1 -gmt 1} msg] $msg + } -result {1 {unable to convert input string: invalid time} 1 {unable to convert input string: invalid time}} +test clock-46.23 {scan: validation rules: invalid day} \ + -body { + list [catch {clock scan "29 Feb 2017" -format "%d %b %Y" -valid 1 -gmt 1} msg] $msg \ + [catch {clock scan "30 Feb 2016" -format "%d %b %Y" -valid 1 -gmt 1} msg] $msg + } -result {1 {unable to convert input string: invalid day} 1 {unable to convert input string: invalid day}} +test clock-46.24 {scan: validation rules: invalid day} \ + -body { + list [catch {clock scan "0 Feb 2017" -format "%d %b %Y" -valid 1 -gmt 1} msg] $msg \ + [catch {clock scan "00 Feb 2017" -format "%d %b %Y" -valid 1 -gmt 1} msg] $msg + } -result {1 {unable to convert input string: invalid day} 1 {unable to convert input string: invalid day}} +test clock-46.25 {scan: validation rules: invalid month} \ + -body { + list [catch {clock scan "13/13/2017" -format "%m/%d/%Y" -valid 1 -gmt 1} msg] $msg \ + [catch {clock scan "00/00/2017" -format "%m/%d/%Y" -valid 1 -gmt 1} msg] $msg + } -result {1 {unable to convert input string: invalid month} 1 {unable to convert input string: invalid month}} +test clock-46.26 {scan: validation rules: ambiguous day} \ + -body { + list [catch {clock scan "1970-01-01--002" -format "%Y-%m-%d--%j" -valid 1 -gmt 1} msg] $msg \ + [catch {clock scan "70-01-01--002" -format "%y-%m-%d--%j" -valid 1 -gmt 1} msg] $msg + } -result {1 {unable to convert input string: ambiguous day} 1 {unable to convert input string: ambiguous day}} +test clock-46.27 {scan: validation rules: ambiguous year} \ + -body { + list [catch {clock scan "19700101 00W014" -format "%Y%m%d %gW%V%u" -valid 1 -gmt 1} msg] $msg \ + [catch {clock scan "1970001 00W014" -format "%Y%j %gW%V%u" -valid 1 -gmt 1} msg] $msg + } -result {1 {unable to convert input string: ambiguous year} 1 {unable to convert input string: ambiguous year}} +test clock-46.28 {scan: validation rules: invalid day of week} \ + -body { + list [catch {clock scan "Sat Jan 01 00:00:00 1970" -format "%a %b %d %H:%M:%S %Y" -valid 1 -gmt 1} msg] $msg + } -result {1 {unable to convert input string: invalid day of week}} +test clock-46.29-1 {scan: validation rules: invalid day of year} \ + -body { + list [catch {clock scan "000-2017" -format "%j-%Y" -valid 1 -gmt 1} msg] $msg \ + [catch {clock scan "366-2017" -format "%j-%Y" -valid 1 -gmt 1} msg] $msg \ + [catch {clock scan "000-2017" -format "%j-%G" -valid 1 -gmt 1} msg] $msg \ + [catch {clock scan "366-2017" -format "%j-%G" -valid 1 -gmt 1} msg] $msg + } -result [lrepeat 4 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 [clock configure -min-year] -max-year [clock configure -max-year] \ + -year-century [clock configure -year-century] -century-switch [clock configure -century-switch]] + clock configure -min-year 2000 -max-year 2100 -year-century 2000 -century-switch 38 + } -body { + list [catch {clock scan "01-01-70" -format "%d-%m-%y" -valid 1 -gmt 1} msg] $msg \ + [catch {clock scan "01-01-1870" -format "%d-%m-%C%y" -valid 1 -gmt 1} msg] $msg \ + [catch {clock scan "01-01-1970" -format "%d-%m-%Y" -valid 1 -gmt 1} msg] $msg \ + } -result [lrepeat 3 1 {unable to convert input string: invalid year}] -cleanup { + clock configure {*}$orgcfg + unset -nocomplain orgcfg + } +test clock-46.31 {scan: validation rules: invalid iso year} -setup { + set orgcfg [list -min-year [clock configure -min-year] -max-year [clock configure -max-year] \ + -year-century [clock configure -year-century] -century-switch [clock configure -century-switch]] + clock configure -min-year 2000 -max-year 2100 -year-century 2000 -century-switch 38 + } -body { + list [catch {clock scan "01-01-70" -format "%d-%m-%g" -valid 1 -gmt 1} msg] $msg \ + [catch {clock scan "01-01-9870" -format "%d-%m-%C%g" -valid 1 -gmt 1} msg] $msg \ + [catch {clock scan "01-01-9870" -format "%d-%m-%G" -valid 1 -gmt 1} msg] $msg \ + } -result [lrepeat 3 1 {unable to convert input string: invalid iso year}] -cleanup { + clock configure {*}$orgcfg + unset -nocomplain orgcfg + } + test clock-47.1 {regression test - four-digit time} { clock scan 0012 } [clock scan 0012 -format %H%M] test clock-47.2 {regression test - four digit time} { clock scan 0039 @@ -36952,16 +38133,23 @@ clock format -0x8000000000000001 -format %s -gmt true } -result {integer value too large to represent} -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} -body { set s 1204049747 @@ -37064,13 +38252,16 @@ msgcat::mclocale $current } -result {1} # cleanup -namespace delete ::testClock ::tcl::clock::ClearCaches +rename test {} +rename __test test ::tcltest::cleanupTests +namespace delete ::testClock + return # Local Variables: # mode: tcl # End: Index: unix/Makefile.in ================================================================== --- unix/Makefile.in +++ unix/Makefile.in @@ -299,11 +299,11 @@ XTTEST_OBJS = xtTestInit.o tclTest.o tclTestObj.o tclTestProcBodyObj.o \ tclThreadTest.o tclUnixTest.o tclXtNotify.o tclXtTest.o GENERIC_OBJS = regcomp.o regexec.o regfree.o regerror.o tclAlloc.o \ tclAssembly.o tclAsync.o tclBasic.o tclBinary.o tclCkalloc.o \ - tclClock.o tclCmdAH.o tclCmdIL.o tclCmdMZ.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 \ tclEnv.o tclEvent.o tclExecute.o tclFCmd.o tclFileName.o tclGet.o \ tclHash.o tclHistory.o tclIndexObj.o tclInterp.o tclIO.o tclIOCmd.o \ @@ -311,11 +311,11 @@ tclLink.o tclListObj.o \ tclLiteral.o tclLoad.o tclMain.o tclNamesp.o tclNotify.o \ tclObj.o tclOptimize.o tclPanic.o tclParse.o tclPathObj.o tclPipe.o \ tclPkg.o tclPkgConfig.o tclPosixStr.o \ tclPreserve.o tclProc.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 \ tclTomMathInterface.o @@ -403,10 +403,11 @@ $(GENERIC_DIR)/tclAsync.c \ $(GENERIC_DIR)/tclBasic.c \ $(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 \ $(GENERIC_DIR)/tclCompCmds.c \ $(GENERIC_DIR)/tclCompCmdsGR.c \ @@ -457,10 +458,11 @@ $(GENERIC_DIR)/tclResolve.c \ $(GENERIC_DIR)/tclResult.c \ $(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)/tclTestObj.c \ $(GENERIC_DIR)/tclTestProcBodyObj.c \ $(GENERIC_DIR)/tclThread.c \ @@ -1138,10 +1140,11 @@ NREHDR = $(GENERIC_DIR)/tclInt.h 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 \ $(GENERIC_DIR)/regc_nfa.c $(GENERIC_DIR)/regc_cvec.c $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/regcomp.c @@ -1174,12 +1177,15 @@ $(CC) -c $(CC_SWITCHES) $(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 tclCmdIL.o: $(GENERIC_DIR)/tclCmdIL.c $(TCLREHDRS) @@ -1186,11 +1192,11 @@ $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tclCmdIL.c 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) $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tclCompCmds.c @@ -1403,10 +1409,13 @@ tclScan.o: $(GENERIC_DIR)/tclScan.c $(CC) -c $(CC_SWITCHES) $(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 tclStubInit.o: $(GENERIC_DIR)/tclStubInit.c Index: win/Makefile.in ================================================================== --- win/Makefile.in +++ win/Makefile.in @@ -237,10 +237,11 @@ tclAsync.$(OBJEXT) \ tclBasic.$(OBJEXT) \ tclBinary.$(OBJEXT) \ tclCkalloc.$(OBJEXT) \ tclClock.$(OBJEXT) \ + tclClockFmt.$(OBJEXT) \ tclCmdAH.$(OBJEXT) \ tclCmdIL.$(OBJEXT) \ tclCmdMZ.$(OBJEXT) \ tclCompCmds.$(OBJEXT) \ tclCompCmdsGR.$(OBJEXT) \ @@ -299,10 +300,11 @@ tclRegexp.$(OBJEXT) \ tclResolve.$(OBJEXT) \ tclResult.$(OBJEXT) \ tclScan.$(OBJEXT) \ tclStringObj.$(OBJEXT) \ + tclStrIdxTree.$(OBJEXT) \ tclStrToD.$(OBJEXT) \ tclStubInit.$(OBJEXT) \ tclThread.$(OBJEXT) \ tclThreadAlloc.$(OBJEXT) \ tclThreadJoin.$(OBJEXT) \ Index: win/makefile.vc ================================================================== --- win/makefile.vc +++ win/makefile.vc @@ -225,10 +225,11 @@ $(TMP_DIR)\tclAsync.obj \ $(TMP_DIR)\tclBasic.obj \ $(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 \ $(TMP_DIR)\tclCompCmds.obj \ $(TMP_DIR)\tclCompCmdsGR.obj \ @@ -287,10 +288,11 @@ $(TMP_DIR)\tclRegexp.obj \ $(TMP_DIR)\tclResolve.obj \ $(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 \ $(TMP_DIR)\tclThreadAlloc.obj \ $(TMP_DIR)\tclThreadJoin.obj \