diff options
author | sebres <sebres@users.sourceforge.net> | 2017-01-10 22:25:20 (GMT) |
---|---|---|
committer | sebres <sebres@users.sourceforge.net> | 2017-01-10 22:25:20 (GMT) |
commit | 7b6cd1089c2d3a6eeb8f12b106af40c18017c8f3 (patch) | |
tree | d85ca1eb9d9794c0a126d12bde28a3b8e74c744b | |
parent | 62ddcc3daa746fc8be02e6b118e0c923ec227793 (diff) | |
download | tcl-7b6cd1089c2d3a6eeb8f12b106af40c18017c8f3.zip tcl-7b6cd1089c2d3a6eeb8f12b106af40c18017c8f3.tar.gz tcl-7b6cd1089c2d3a6eeb8f12b106af40c18017c8f3.tar.bz2 |
l10n (with caching) implemented, msgcat package optimized, code review, etc.
-rw-r--r-- | generic/tclClock.c | 448 | ||||
-rw-r--r-- | generic/tclClockFmt.c | 193 | ||||
-rw-r--r-- | generic/tclDate.h | 45 | ||||
-rwxr-xr-x | library/clock.tcl | 79 | ||||
-rw-r--r-- | library/msgcat/msgcat.tcl | 113 | ||||
-rw-r--r-- | tests-perf/clock.perf.tcl | 31 | ||||
-rw-r--r-- | tests/clock.test | 8 |
7 files changed, 608 insertions, 309 deletions
diff --git a/generic/tclClock.c b/generic/tclClock.c index 1e9abba..166a4b3 100644 --- a/generic/tclClock.c +++ b/generic/tclClock.c @@ -59,8 +59,7 @@ typedef enum ClockLiteral { LIT_TZDATA, LIT_GETSYSTEMTIMEZONE, LIT_SETUPTIMEZONE, - LIT_GETSYSTEMLOCALE, - LIT_SETUPLOCALE, + LIT_MCGET, LIT_TCL_CLOCK, #if 0 LIT_FREESCAN, #endif @@ -83,13 +82,17 @@ static const char *const Literals[] = { "::tcl::clock::TZData", "::tcl::clock::GetSystemTimeZone", "::tcl::clock::SetupTimeZone", - "::tcl::clock::mclocale", - "::tcl::clock::EnterLocale", + "::msgcat::mcget", "::tcl::clock" #if 0 "::tcl::clock::FreeScan" #endif }; +/* 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 }; /* @@ -257,9 +260,10 @@ TclClockInit( data->refCount = 0; data->literals = 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->LastTZEpoch = 0; data->currentYearCentury = ClockDefaultYearCentury; data->yearOfCenturySwitch = ClockDefaultCenturySwitch; @@ -273,6 +277,12 @@ TclClockInit( data->LastSetupTimeZone = NULL; data->LastSetupTZData = NULL; + data->CurrentLocale = NULL; + data->CurrentLocaleDict = NULL; + data->LastUnnormUsedLocale = NULL; + data->LastUsedLocale = NULL; + data->LastUsedLocaleDict = NULL; + data->lastBase.timezoneObj = NULL; data->UTC2Local.timezoneObj = NULL; data->UTC2Local.tzName = NULL; @@ -323,6 +333,12 @@ ClockConfigureClear( Tcl_UnsetObjRef(data->LastSetupTimeZone); Tcl_UnsetObjRef(data->LastSetupTZData); + Tcl_UnsetObjRef(data->CurrentLocale); + Tcl_UnsetObjRef(data->CurrentLocaleDict); + Tcl_UnsetObjRef(data->LastUnnormUsedLocale); + Tcl_UnsetObjRef(data->LastUsedLocale); + Tcl_UnsetObjRef(data->LastUsedLocaleDict); + Tcl_UnsetObjRef(data->lastBase.timezoneObj); Tcl_UnsetObjRef(data->UTC2Local.timezoneObj); Tcl_UnsetObjRef(data->UTC2Local.tzName); @@ -341,6 +357,18 @@ ClockDeleteCmdProc( 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); @@ -355,9 +383,9 @@ ClockDeleteCmdProc( inline Tcl_Obj * NormTimezoneObj( ClockClientData *dataPtr, /* Client data containing literal pool */ - Tcl_Obj * timezoneObj) + Tcl_Obj *timezoneObj) { - const char * tz; + const char *tz; if ( timezoneObj == dataPtr->LastUnnormSetupTimeZone && dataPtr->LastSetupTimeZone != NULL ) { @@ -399,6 +427,208 @@ NormTimezoneObj( /* *---------------------------------------------------------------------- */ +static Tcl_Obj * +NormLocaleObj( + ClockClientData *dataPtr, /* Client data containing literal pool */ + Tcl_Obj *localeObj, + Tcl_Obj **mcDictObj) +{ + const char *loc; + if ( localeObj == NULL || localeObj == dataPtr->CurrentLocale + || localeObj == dataPtr->literals[LIT_C] + ) { + *mcDictObj = dataPtr->CurrentLocaleDict; + return dataPtr->CurrentLocale; + } + if ( localeObj == dataPtr->LastUsedLocale + || localeObj == dataPtr->LastUnnormUsedLocale + ) { + *mcDictObj = dataPtr->LastUsedLocaleDict; + return dataPtr->LastUsedLocale; + } + + loc = TclGetString(localeObj); + if (dataPtr->CurrentLocale != NULL && + (localeObj == dataPtr->CurrentLocale + || strcmp(loc, TclGetString(dataPtr->CurrentLocale)) == 0 + ) + ) { + *mcDictObj = dataPtr->CurrentLocaleDict; + localeObj = dataPtr->CurrentLocale; + } + else + if (dataPtr->LastUsedLocale != NULL && + (localeObj == dataPtr->LastUsedLocale + || strcmp(loc, TclGetString(dataPtr->LastUsedLocale)) == 0 + ) + ) { + *mcDictObj = dataPtr->LastUsedLocaleDict; + Tcl_SetObjRef(dataPtr->LastUnnormUsedLocale, localeObj); + localeObj = dataPtr->LastUsedLocale; + } + else + if ( + strcmp(loc, Literals[LIT_C]) == 0 + ) { + *mcDictObj = dataPtr->CurrentLocaleDict; + localeObj = dataPtr->CurrentLocale; + } + else + { + *mcDictObj = NULL; + } + return localeObj; +} + +/* + *---------------------------------------------------------------------- + */ +MODULE_SCOPE Tcl_Obj * +ClockMCDict(ClockFmtScnCmdArgs *opts) +{ + ClockClientData *dataPtr = opts->clientData; + Tcl_Obj *callargs[3]; + + /* if dict not yet retrieved */ + if (opts->mcDictObj == NULL) { + + /* if locale was not yet used */ + if ( !(opts->flags & CLF_LOCALE_USED) ) { + + opts->localeObj = NormLocaleObj(opts->clientData, + opts->localeObj, &opts->mcDictObj); + + if (opts->localeObj == NULL) { + Tcl_SetResult(opts->interp, + "locale not specified and no default locale set", TCL_STATIC); + Tcl_SetErrorCode(opts->interp, "CLOCK", "badOption", NULL); + return NULL; + } + opts->flags |= CLF_LOCALE_USED; + + /* check locale literals already available (on demand creation) */ + if (dataPtr->mcLiterals == NULL) { + int i; + dataPtr->mcLiterals = ckalloc(MCLIT__END * sizeof(Tcl_Obj*)); + for (i = 0; i < MCLIT__END; ++i) { + Tcl_InitObjRef(dataPtr->mcLiterals[i], + Tcl_NewStringObj(MsgCtLiterals[i], -1)); + } + } + } + + if (opts->mcDictObj == NULL) { + /* get msgcat dictionary - ::msgcat::mcget ::tcl::clock locale */ + callargs[0] = dataPtr->literals[LIT_MCGET]; + callargs[1] = dataPtr->literals[LIT_TCL_CLOCK]; + callargs[2] = opts->localeObj; + + if (Tcl_EvalObjv(opts->interp, 3, callargs, 0) != TCL_OK) { + return NULL; + } + + opts->mcDictObj = Tcl_GetObjResult(opts->interp); + if ( opts->localeObj == dataPtr->CurrentLocale ) { + Tcl_SetObjRef(dataPtr->CurrentLocaleDict, opts->mcDictObj); + } else { + Tcl_SetObjRef(dataPtr->LastUsedLocale, opts->localeObj); + Tcl_UnsetObjRef(dataPtr->LastUnnormUsedLocale); + Tcl_SetObjRef(dataPtr->LastUsedLocaleDict, opts->mcDictObj); + } + } + } + + return opts->mcDictObj; +} + +MODULE_SCOPE Tcl_Obj * +ClockMCGet( + ClockFmtScnCmdArgs *opts, + int mcKey) +{ + ClockClientData *dataPtr = 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 */ +} + +MODULE_SCOPE Tcl_Obj * +ClockMCGetListIdxDict( + ClockFmtScnCmdArgs *opts, + int mcKey) +{ + ClockClientData *dataPtr = opts->clientData; + + Tcl_Obj *valObj = NULL; + + if (opts->mcDictObj == NULL) { + ClockMCDict(opts); + if (opts->mcDictObj == NULL) + return NULL; + } + + /* try to get indices dictionray, + * if not available - create from list */ + + if (Tcl_DictObjGet(NULL, opts->mcDictObj, + dataPtr->mcLitIdxs[mcKey], &valObj) != TCL_OK + ) { + Tcl_Obj **lstv, *intObj; + int i, lstc; + + if (dataPtr->mcLitIdxs == NULL) { + dataPtr->mcLitIdxs = ckalloc(MCLIT__END * sizeof(Tcl_Obj*)); + for (i = 0; i < MCLIT__END; ++i) { + Tcl_InitObjRef(dataPtr->mcLitIdxs[i], + Tcl_NewStringObj(MsgCtLitIdxs[i], -1)); + } + } + + if (Tcl_DictObjGet(opts->interp, opts->mcDictObj, + dataPtr->mcLiterals[mcKey], &valObj) != TCL_OK) { + return NULL; + }; + if (TclListObjGetElements(opts->interp, valObj, + &lstc, &lstv) != TCL_OK) { + return NULL; + }; + + valObj = Tcl_NewDictObj(); + for (i = 0; i < lstc; i++) { + intObj = Tcl_NewIntObj(i); + if (Tcl_DictObjPut(opts->interp, valObj, + lstv[i], intObj) != TCL_OK + ) { + Tcl_DecrRefCount(valObj); + Tcl_DecrRefCount(intObj); + return NULL; + } + }; + + if (Tcl_DictObjPut(opts->interp, opts->mcDictObj, + dataPtr->mcLitIdxs[mcKey], valObj) != TCL_OK + ) { + Tcl_DecrRefCount(valObj); + return NULL; + } + }; + + return valObj; +} + +/* + *---------------------------------------------------------------------- + */ static int ClockConfigureObjCmd( ClientData clientData, /* Client data containing literal pool */ @@ -410,23 +640,25 @@ ClockConfigureObjCmd( Tcl_Obj **litPtr = dataPtr->literals; static const char *const options[] = { - "-system-tz", "-setup-tz", "-clear", + "-system-tz", "-setup-tz", "-default-locale", + "-clear", "-year-century", "-century-switch", NULL }; enum optionInd { - CLOCK_SYSTEM_TZ, CLOCK_SETUP_TZ, CLOCK_CLEAR_CACHE, + CLOCK_SYSTEM_TZ, CLOCK_SETUP_TZ, CLOCK_CURRENT_LOCALE, + CLOCK_CLEAR_CACHE, CLOCK_YEAR_CENTURY, CLOCK_CENTURY_SWITCH, CLOCK_SETUP_GMT, CLOCK_SETUP_NOP }; int optionIndex; /* Index of an option. */ int i; - for (i = 1; i < objc; i+=2) { - if (Tcl_GetIndexFromObj(interp, objv[i], options, + 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]), NULL); + Tcl_GetString(objv[i-1]), NULL); return TCL_ERROR; } switch (optionIndex) { @@ -434,24 +666,24 @@ ClockConfigureObjCmd( if (1) { /* validate current tz-epoch */ unsigned long lastTZEpoch = TzsetGetEpoch(); - if (i+1 < objc) { - if (dataPtr->SystemTimeZone != objv[i+1]) { - Tcl_SetObjRef(dataPtr->SystemTimeZone, objv[i+1]); + if (i < objc) { + if (dataPtr->SystemTimeZone != objv[i]) { + Tcl_SetObjRef(dataPtr->SystemTimeZone, objv[i]); Tcl_UnsetObjRef(dataPtr->SystemSetupTZData); } dataPtr->LastTZEpoch = lastTZEpoch; } - if (dataPtr->SystemTimeZone != NULL + if (i+1 >= objc && dataPtr->SystemTimeZone != NULL && dataPtr->LastTZEpoch == lastTZEpoch) { Tcl_SetObjResult(interp, dataPtr->SystemTimeZone); } } break; case CLOCK_SETUP_TZ: - if (i+1 < objc) { + if (i < objc) { /* differentiate GMT and system zones, because used often */ - Tcl_Obj *timezoneObj = NormTimezoneObj(dataPtr, objv[i+1]); - Tcl_SetObjRef(dataPtr->LastUnnormSetupTimeZone, objv[i+1]); + Tcl_Obj *timezoneObj = NormTimezoneObj(dataPtr, objv[i]); + Tcl_SetObjRef(dataPtr->LastUnnormSetupTimeZone, objv[i]); if (dataPtr->LastSetupTimeZone != timezoneObj) { Tcl_SetObjRef(dataPtr->LastSetupTimeZone, timezoneObj); Tcl_UnsetObjRef(dataPtr->LastSetupTZData); @@ -463,7 +695,7 @@ ClockConfigureObjCmd( } switch (optionIndex) { case CLOCK_SETUP_GMT: - if (i+1 < objc) { + if (i < objc) { if (dataPtr->GMTSetupTimeZone != timezoneObj) { Tcl_SetObjRef(dataPtr->GMTSetupTimeZone, timezoneObj); Tcl_UnsetObjRef(dataPtr->GMTSetupTZData); @@ -471,7 +703,7 @@ ClockConfigureObjCmd( } break; case CLOCK_SETUP_TZ: - if (i+1 < objc) { + if (i < objc) { if (dataPtr->AnySetupTimeZone != timezoneObj) { Tcl_SetObjRef(dataPtr->AnySetupTimeZone, timezoneObj); Tcl_UnsetObjRef(dataPtr->AnySetupTZData); @@ -480,35 +712,52 @@ ClockConfigureObjCmd( break; } } - if (dataPtr->LastSetupTimeZone != NULL) { + if (i+1 >= objc && dataPtr->LastSetupTimeZone != NULL) { Tcl_SetObjResult(interp, dataPtr->LastSetupTimeZone); } break; + case CLOCK_CURRENT_LOCALE: + if (i < objc) { + if (dataPtr->CurrentLocale != objv[i]) { + Tcl_SetObjRef(dataPtr->CurrentLocale, objv[i]); + Tcl_UnsetObjRef(dataPtr->CurrentLocaleDict); + } + } + if (i+1 >= objc && dataPtr->CurrentLocale != NULL) { + Tcl_SetObjResult(interp, dataPtr->CurrentLocale); + } + break; case CLOCK_YEAR_CENTURY: - if (i+1 < objc) { + if (i < objc) { int year; - if (TclGetIntFromObj(interp, objv[i+1], &year) != TCL_OK) { + if (TclGetIntFromObj(interp, objv[i], &year) != TCL_OK) { return TCL_ERROR; } dataPtr->currentYearCentury = year; - Tcl_SetObjResult(interp, objv[i+1]); + if (i+1 >= objc) { + Tcl_SetObjResult(interp, objv[i]); + } continue; - } - Tcl_SetObjResult(interp, - Tcl_NewIntObj(dataPtr->currentYearCentury)); + } + if (i+1 >= objc) { + Tcl_SetObjResult(interp, + Tcl_NewIntObj(dataPtr->currentYearCentury)); + } break; case CLOCK_CENTURY_SWITCH: - if (i+1 < objc) { + if (i < objc) { int year; - if (TclGetIntFromObj(interp, objv[i+1], &year) != TCL_OK) { + if (TclGetIntFromObj(interp, objv[i], &year) != TCL_OK) { return TCL_ERROR; } dataPtr->yearOfCenturySwitch = year; - Tcl_SetObjResult(interp, objv[i+1]); + Tcl_SetObjResult(interp, objv[i]); continue; - } - Tcl_SetObjResult(interp, - Tcl_NewIntObj(dataPtr->yearOfCenturySwitch)); + } + if (i+1 >= objc) { + Tcl_SetObjResult(interp, + Tcl_NewIntObj(dataPtr->yearOfCenturySwitch)); + } break; case CLOCK_CLEAR_CACHE: ClockConfigureClear(dataPtr); @@ -597,7 +846,7 @@ ClockGetSystemTimeZone( } Tcl_UnsetObjRef(dataPtr->SystemTimeZone); - Tcl_UnsetObjRef(data->SystemSetupTZData); + Tcl_UnsetObjRef(dataPtr->SystemSetupTZData); literals = dataPtr->literals; @@ -683,125 +932,6 @@ ClockFormatNumericTimeZone(int z) { } return Tcl_ObjPrintf("%c%02d%02d", sign, h, m); } - -/* - *---------------------------------------------------------------------- - */ -inline Tcl_Obj * -NormLocaleObj( - ClockClientData *dataPtr, /* Client data containing literal pool */ - Tcl_Obj *localeObj) -{ - const char * tz; - if ( localeObj == dataPtr->LastUnnormSetupLocale - && dataPtr->LastSetupLocale != NULL - ) { - return dataPtr->LastSetupLocale; - } - if ( localeObj == dataPtr->LastSetupLocale - || localeObj == dataPtr->literals[LIT_CLOCALE] - || localeObj == dataPtr->SystemLocale - || localeObj == dataPtr->AnySetupLocale - ) { - return localeObj; - } - - tz = TclGetString(localeObj); - if (dataPtr->AnySetupLocale != NULL && - (localeObj == dataPtr->AnySetupLocale - || strcmp(tz, TclGetString(dataPtr->AnySetupLocale)) == 0 - ) - ) { - localeObj = dataPtr->AnySetupLocale; - } - else - if (dataPtr->SystemLocale != NULL && - (localeObj == dataPtr->SystemLocale - || strcmp(tz, TclGetString(dataPtr->SystemLocale)) == 0 - ) - ) { - localeObj = dataPtr->SystemLocale; - } - else - if ( - strcmp(tz, Literals[LIT_GMT]) == 0 - ) { - localeObj = dataPtr->literals[LIT_GMT]; - } - return localeObj; -} -/* - *---------------------------------------------------------------------- - */ -static Tcl_Obj * -ClockGetSystemLocale( - ClientData clientData, /* Opaque pointer to literal pool, etc. */ - Tcl_Interp *interp) /* Tcl interpreter */ -{ - ClockClientData *dataPtr = clientData; - Tcl_Obj **literals; - - /* if known (cached) - return now */ - if (dataPtr->SystemLocale != NULL) { - return dataPtr->SystemLocale; - } - - literals = dataPtr->literals; - - if (Tcl_EvalObjv(interp, 1, &literals[LIT_GETSYSTEMLOCALE], 0) != TCL_OK) { - return NULL; - } - Tcl_SetObjRef(dataPtr->SystemLocale, Tcl_GetObjResult(interp)); - return dataPtr->SystemLocale; -} -/* - *---------------------------------------------------------------------- - */ -static Tcl_Obj * -ClockSetupLocale( - ClientData clientData, /* Opaque pointer to literal pool, etc. */ - Tcl_Interp *interp, /* Tcl interpreter */ - Tcl_Obj *localeObj) -{ - ClockClientData *dataPtr = clientData; - Tcl_Obj **literals = dataPtr->literals; - Tcl_Obj *callargs[2]; - - if (localeObj == NULL || localeObj == dataPtr->SystemLocale) { - if (dataPtr->SystemLocale == NULL) { - return ClockGetSystemLocale(clientData, interp); - } - return dataPtr->SystemLocale; - } - -#if 0 - /* if cached (if already setup this one) */ - if ( dataPtr->LastSetupTimeZone != NULL - && ( localeObj == dataPtr->LastSetupTimeZone - || timezoneObj == dataPtr->LastUnnormSetupTimeZone - ) - ) { - return dataPtr->LastSetupTimeZone; - } - - /* differentiate GMT and system zones, because used often and already set */ - timezoneObj = NormTimezoneObj(dataPtr, timezoneObj); - if ( timezoneObj == dataPtr->GMTSetupTimeZone - || timezoneObj == dataPtr->SystemTimeZone - || timezoneObj == dataPtr->AnySetupTimeZone - ) { - return timezoneObj; - } - -#endif - callargs[0] = literals[LIT_SETUPLOCALE]; - callargs[1] = localeObj; - - if (Tcl_EvalObjv(interp, 2, callargs, 0) == TCL_OK) { - return localeObj; // dataPtr->CurrentLocale; - } - return NULL; -} /* *---------------------------------------------------------------------- * @@ -2741,19 +2871,13 @@ ClockScanObjCmd( } } - /* Setup timezone and locale */ + /* Setup timezone (normalize object id needed and load TZ on demand) */ opts.timezoneObj = ClockSetupTimeZone(clientData, interp, opts.timezoneObj); if (opts.timezoneObj == NULL) { return TCL_ERROR; } - opts.localeObj = ClockSetupLocale(clientData, interp, opts.localeObj); - if (opts.localeObj == NULL) { - return TCL_ERROR; - } - - ClockInitDateInfo(info); yydate.tzName = NULL; diff --git a/generic/tclClockFmt.c b/generic/tclClockFmt.c index 17181d9..a3c7f20 100644 --- a/generic/tclClockFmt.c +++ b/generic/tclClockFmt.c @@ -454,45 +454,55 @@ Tcl_GetClockFrmScnFromObj( return fss; } - + +/* + * DetermineGreedySearchLen -- + * + * Determine min/max lengths as exact as possible (speed, greedy match) + * + */ +inline +void DetermineGreedySearchLen(ClockFmtScnCmdArgs *opts, + DateInfo *info, ClockScanToken *tok, int *minLen, int *maxLen) +{ + register const char*p = yyInput; + *minLen = 0; + *maxLen = info->dateEnd - p; + + /* if no tokens anymore */ + if (!(tok+1)->map) { + /* should match to end or first space */ + while (!isspace(UCHAR(*p)) && ++p < info->dateEnd) {}; + *minLen = p - yyInput; + } +} static int LocaleListSearch(ClockFmtScnCmdArgs *opts, - DateInfo *info, const char * mcKey, int *val) + DateInfo *info, int mcKey, int *val, + int minLen, int maxLen) { - Tcl_Obj *callargs[4] = {NULL, NULL, NULL, NULL}, **lstv; - int i, lstc; - Tcl_Obj *valObj = NULL; - int ret = TCL_RETURN; + Tcl_Obj **lstv; + int lstc, i, l; + const char *s; + Tcl_Obj *valObj; /* get msgcat value */ - TclNewLiteralStringObj(callargs[0], "::msgcat::mcget"); - TclNewLiteralStringObj(callargs[1], "::tcl::clock"); - callargs[2] = opts->localeObj; - if (opts->localeObj == NULL) { - TclNewLiteralStringObj(callargs[1], "c"); - } - callargs[3] = Tcl_NewStringObj(mcKey, -1); - - for (i = 0; i < 4; i++) { - Tcl_IncrRefCount(callargs[i]); - } - if (Tcl_EvalObjv(opts->interp, 4, callargs, 0) != TCL_OK) { - goto done; + valObj = ClockMCGet(opts, mcKey); + if (valObj == NULL) { + return TCL_ERROR; } - Tcl_InitObjRef(valObj, Tcl_GetObjResult(opts->interp)); - /* is a list */ if (TclListObjGetElements(opts->interp, valObj, &lstc, &lstv) != TCL_OK) { - goto done; + return TCL_ERROR; } /* search in list */ for (i = 0; i < lstc; i++) { - const char *s = TclGetString(lstv[i]); - int l = lstv[i]->length; - if ( l <= info->dateEnd - yyInput + s = TclGetString(lstv[i]); + l = lstv[i]->length; + if ( l >= minLen && l <= maxLen && strncasecmp(yyInput, s, l) == 0 ) { *val = i; @@ -501,21 +511,11 @@ LocaleListSearch(ClockFmtScnCmdArgs *opts, } } - ret = TCL_OK; /* if not found */ - if (i >= lstc) { - ret = TCL_ERROR; - } - -done: - - Tcl_UnsetObjRef(valObj); - - for (i = 0; i < 4; i++) { - Tcl_UnsetObjRef(callargs[i]); + if (i < lstc) { + return TCL_OK; } - - return ret; + return TCL_RETURN; } static int @@ -535,12 +535,34 @@ StaticListSearch(ClockFmtScnCmdArgs *opts, } s++; } - if (*s == NULL) { - return TCL_ERROR; + if (*s != NULL) { + return TCL_OK; } - - return TCL_OK; -}; + return TCL_RETURN; +} + +inline const char * +FindWordEnd( + ClockScanToken *tok, + register const char * p, const char * end) +{ + register const char *x = tok->tokWord.start; + if (x == tok->tokWord.end) { /* single char word */ + if (*p != *x) { + /* no match -> error */ + return NULL; + } + return ++p; + } + /* multi-char word */ + while (*p++ == *x++) { + if (x >= tok->tokWord.end || p >= end) { + /* no match -> error */ + return NULL; + } + }; + return p; +} static int ClockScnToken_Month_Proc(ClockFmtScnCmdArgs *opts, @@ -560,19 +582,26 @@ ClockScnToken_Month_Proc(ClockFmtScnCmdArgs *opts, }; int val; if (StaticListSearch(opts, info, months, &val) != TCL_OK) { - return TCL_ERROR; + return TCL_RETURN; } yyMonth = (val % 12) + 1; return TCL_OK; */ - int val; - int ret = LocaleListSearch(opts, info, "MONTHS_FULL", &val); + int ret, val; + int minLen; + int maxLen; + + DetermineGreedySearchLen(opts, info, tok, &minLen, &maxLen); + + ret = LocaleListSearch(opts, info, MCLIT_MONTHS_FULL, &val, + minLen, maxLen); if (ret != TCL_OK) { + /* if not found */ if (ret == TCL_RETURN) { - return ret; + ret = LocaleListSearch(opts, info, MCLIT_MONTHS_ABBREV, &val, + minLen, maxLen); } - ret = LocaleListSearch(opts, info, "MONTHS_ABBREV", &val); if (ret != TCL_OK) { return ret; } @@ -588,9 +617,14 @@ static int ClockScnToken_LocaleListMatcher_Proc(ClockFmtScnCmdArgs *opts, DateInfo *info, ClockScanToken *tok) { - int val; + int ret, val; + int minLen; + int maxLen; + + DetermineGreedySearchLen(opts, info, tok, &minLen, &maxLen); - int ret = LocaleListSearch(opts, info, (char *)tok->map->data, &val); + ret = LocaleListSearch(opts, info, (int)tok->map->data, &val, + minLen, maxLen); if (ret != TCL_OK) { return ret; } @@ -639,8 +673,8 @@ static ClockScanTokenMap ScnSTokenMap[] = { NULL}, }; static const char *ScnSTokenWrapMapIndex[2] = { - "eBh", - "dbb" + "eNBh", + "dmbb" }; static const char *ScnETokenMapIndex = @@ -654,11 +688,14 @@ static const char *ScnETokenWrapMapIndex[2] = { }; static const char *ScnOTokenMapIndex = - "d"; + "dm"; static ClockScanTokenMap ScnOTokenMap[] = { /* %Od %Oe */ {CTOKT_PARSER, CLF_DATE, 0, 0, TclOffset(DateInfo, date.dayOfMonth), - ClockScnToken_LocaleListMatcher_Proc, "LOCALE_NUMERALS"}, + ClockScnToken_LocaleListMatcher_Proc, (void *)MCLIT_LOCALE_NUMERALS}, + /* %Om */ + {CTOKT_PARSER, CLF_DATE, 0, 0, TclOffset(DateInfo, date.month), + ClockScnToken_LocaleListMatcher_Proc, (void *)MCLIT_LOCALE_NUMERALS}, }; static const char *ScnOTokenWrapMapIndex[2] = { "e", @@ -730,7 +767,7 @@ ClockGetOrParseScanFormat( switch (*p) { case '%': if (1) { - ClockScanTokenMap * maps = ScnSTokenMap; + ClockScanTokenMap * scnMap = ScnSTokenMap; const char *mapIndex = ScnSTokenMapIndex, **wrapIndex = ScnSTokenWrapMapIndex; if (p+1 >= e) { @@ -748,13 +785,13 @@ ClockGetOrParseScanFormat( continue; break; case 'E': - maps = ScnETokenMap, + scnMap = ScnETokenMap, mapIndex = ScnETokenMapIndex, wrapIndex = ScnETokenWrapMapIndex; p++; break; case 'O': - maps = ScnOTokenMap, + scnMap = ScnOTokenMap, mapIndex = ScnOTokenMapIndex, wrapIndex = ScnOTokenWrapMapIndex; p++; @@ -778,7 +815,7 @@ ClockGetOrParseScanFormat( goto word_tok; } } - tok->map = &ScnSTokenMap[cp - mapIndex]; + tok->map = &scnMap[cp - mapIndex]; tok->tokWord.start = p; /* calculate look ahead value by standing together tokens */ if (tok > fss->scnTok) { @@ -928,7 +965,7 @@ ClockScan( size = p - yyInput; if (size < map->minSize) { /* missing input -> error */ - goto error; + goto not_match; } /* string 2 number, put number into info structure by offset */ p = yyInput; x = p + size; @@ -953,10 +990,10 @@ ClockScan( case TCL_OK: break; case TCL_RETURN: - goto done; + goto not_match; break; default: - goto error; + goto done; break; }; p = yyInput; @@ -967,7 +1004,7 @@ ClockScan( if (opts->flags & CLF_STRICT) { if (!isspace(UCHAR(*p))) { /* unmatched -> error */ - goto error; + goto not_match; } p++; } @@ -976,21 +1013,13 @@ ClockScan( } break; case CTOKT_WORD: - x = tok->tokWord.start; - if (x == tok->tokWord.end) { /* single char word */ - if (*p != *x) { - /* no match -> error */ - goto error; - } - p++; - continue; - } - /* multi-char word */ - while (p < end && x < tok->tokWord.end && *p++ == *x++) {}; - if (x < tok->tokWord.end) { + x = FindWordEnd(tok, p, end); + if (!x) { /* no match -> error */ - goto error; + goto not_match; } + p = x; + continue; break; } } @@ -1002,7 +1031,7 @@ ClockScan( /* check end was reached */ if (p < end) { /* something after last token - wrong format */ - goto error; + goto not_match; } /* invalidate result */ @@ -1045,15 +1074,15 @@ ClockScan( overflow: - Tcl_SetObjResult(interp, Tcl_NewStringObj( - "integer value too large to represent", -1)); - Tcl_SetErrorCode(interp, "CLOCK", "integervalueTooLarge", NULL); + Tcl_SetResult(interp, "requested date too large to represent", + TCL_STATIC); + Tcl_SetErrorCode(interp, "CLOCK", "dateTooLarge", NULL); goto done; -error: +not_match: - Tcl_SetResult(interp, - "input string does not match supplied format", TCL_STATIC); + Tcl_SetResult(interp, "input string does not match supplied format", + TCL_STATIC); Tcl_SetErrorCode(interp, "CLOCK", "badInputString", NULL); done: diff --git a/generic/tclDate.h b/generic/tclDate.h index d97cd3c..1c51a02 100644 --- a/generic/tclDate.h +++ b/generic/tclDate.h @@ -35,6 +35,22 @@ #define CLF_INVALIDATE_JULIANDAY (1 << 7) /* assemble julianDay using year, month, etc. */ #define CLF_INVALIDATE_SECONDS (1 << 8) /* assemble localSeconds (and seconds at end) */ + +/* + * Enumeration of the msgcat literals used in [clock] + */ + +typedef enum ClockMsgCtLiteral { + MCLIT_MONTHS_FULL, MCLIT_MONTHS_ABBREV, + MCLIT_LOCALE_NUMERALS, + MCLIT__END +} ClockMsgCtLiteral; + +#define CLOCK_LOCALE_LITERAL_ARRAY(litarr, pref) static const char *const litarr[] = { \ + pref "MONTHS_FULL", pref "MONTHS_ABBREV", \ + pref "LOCALE_NUMERALS", \ +} + /* * Primitives to safe set, reset and free references. */ @@ -176,16 +192,19 @@ ClockInitDateInfo(DateInfo *info) { #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 */ + 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 only) */ int flags; /* Flags control scanning */ + + Tcl_Obj *mcDictObj; /* Current dictionary of tcl::clock package for given localeObj*/ } ClockFmtScnCmdArgs; /* @@ -194,7 +213,11 @@ typedef struct ClockFmtScnCmdArgs { typedef struct ClockClientData { int refCount; /* Number of live references. */ - Tcl_Obj **literals; /* Pool of object literals. */ + 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 */ + /* Cache for current clock parameters, imparted via "configure" */ unsigned long LastTZEpoch; int currentYearCentury; @@ -208,6 +231,13 @@ typedef struct ClockClientData { Tcl_Obj *LastUnnormSetupTimeZone; Tcl_Obj *LastSetupTimeZone; Tcl_Obj *LastSetupTZData; + + Tcl_Obj *CurrentLocale; + Tcl_Obj *CurrentLocaleDict; + Tcl_Obj *LastUnnormUsedLocale; + Tcl_Obj *LastUsedLocale; + Tcl_Obj *LastUsedLocaleDict; + /* Cache for last base (last-second fast convert if base/tz not changed) */ struct { Tcl_Obj *timezoneObj; @@ -328,6 +358,15 @@ MODULE_SCOPE time_t ToSeconds(time_t Hours, time_t Minutes, MODULE_SCOPE int TclClockFreeScan(Tcl_Interp *interp, DateInfo *info); +/* tclClock.c module declarations */ + +MODULE_SCOPE Tcl_Obj * + ClockMCDict(ClockFmtScnCmdArgs *opts); +MODULE_SCOPE Tcl_Obj * + ClockMCGet(ClockFmtScnCmdArgs *opts, int mcKey); +MODULE_SCOPE Tcl_Obj * + ClockMCGetListIdxDict(ClockFmtScnCmdArgs *opts, int mcKey); + /* tclClockFmt.c module declarations */ MODULE_SCOPE ClockFmtScnStorage * diff --git a/library/clock.tcl b/library/clock.tcl index ebbecb9..b4632b1 100755 --- a/library/clock.tcl +++ b/library/clock.tcl @@ -289,8 +289,9 @@ proc ::tcl::clock::Initialize {} { # Default configuration - # configure -year-century 2000 \ - # -century-switch 38 + configure -default-locale [mclocale] + #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 @@ -2105,6 +2106,51 @@ proc ::tcl::clock::MakeParseCodeFromFields { dateFields parseActions } { #---------------------------------------------------------------------- # +# GetSystemTimeZone -- +# +# Determines the system time zone, which is the default for the +# 'clock' command if no other zone is supplied. +# +# Parameters: +# None. +# +# Results: +# Returns the system time zone. +# +# Side effects: +# Stores the sustem time zone in engine configuration, since +# determining it may be an expensive process. +# +#---------------------------------------------------------------------- + +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 -- # # Switch [mclocale] to a given locale if necessary @@ -2121,33 +2167,12 @@ proc ::tcl::clock::MakeParseCodeFromFields { dateFields parseActions } { #---------------------------------------------------------------------- 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 } diff --git a/library/msgcat/msgcat.tcl b/library/msgcat/msgcat.tcl index 7f23568..3fd2cdb 100644 --- a/library/msgcat/msgcat.tcl +++ b/library/msgcat/msgcat.tcl @@ -227,52 +227,61 @@ proc msgcat::mc {src args} { # msgcat::mcget -- # -# Find the translation for the given string based on the given -# locale setting. Check the given namespace first, then look in each -# parent namespace until the source is found. If additional args are -# specified, use the format command to work them into the traslated -# string. -# If no catalog item is found, mcunknown is called in the caller frame -# and its result is returned. +# Return the translation for the given string based on the given +# locale setting or the whole dictionary object of the package/locale. +# Searching of catalog is similar to "msgcat::mc". +# +# Contrary to "msgcat::mc" may additionally load a package catalog +# on demand. # # Arguments: -# src The string to translate. -# args Args to pass to the format command +# ns The package namespace (as catalog selector). +# loc The locale used for translation. +# {src} The string to translate. +# {args} Args to pass to the format command # # Results: # Returns the translated string. Propagates errors thrown by the # format command. -proc msgcat::mcget {ns loc src args} { +proc msgcat::mcget {ns loc args} { variable Msgs if {$loc eq {C}} { set loclist [PackagePreferences $ns] + set loc [lindex $loclist 0] } else { + set loc [string tolower $loc] variable PackageConfig - # if {![dict exists $PackageConfig $ns $loc]} { - # set loc [mclocale] - # } - set loclist [dict get $PackageConfig locales $ns $loc] + # get locales list for given locale (de_de -> {de_de de {}}) + if {[catch { + set loclist [dict get $PackageConfig locales $ns $loc] + }]} { + # lazy load catalog on demand + mcpackagelocale load $loc $ns + set loclist [dict get $PackageConfig locales $ns $loc] + } } + if {![llength $args]} { + # get whole catalog: + return [msgcat::Merge $ns $loclist] + } + set src [lindex $args 0] + # search translation for each locale (regarding parent namespaces) for {set nscur $ns} {$nscur != ""} {set nscur [namespace parent $nscur]} { foreach loc $loclist { if {[dict exists $Msgs $nscur $loc $src]} { - if {[llength $args]} { - return [format [dict get $Msgs $nscur $loc $src] {*}$args] + if {[llength $args] > 1} { + return [format [dict get $Msgs $nscur $loc $src] \ + {*}[lrange $args 1 end]] } else { return [dict get $Msgs $nscur $loc $src] } } } } - # call package local or default unknown command - set args [linsert $args 0 $loclist $src] - switch -exact -- [Invoke unknowncmd $args $ns result 1] { - 0 { return [uplevel 1 [linsert $args 0 [namespace origin mcunknown]]] } - 1 { return [DefaultUnknown {*}$args] } - default { return $result } - } + # get with package default locale + mcget $ns [lindex $loclist 0] {*}$args } # msgcat::mcexists -- @@ -465,6 +474,10 @@ proc msgcat::mcloadedlocales {subcommand} { # items, if the former locale was the default locale. # Returns the normalized set locale. # The default locale is taken, if locale is not given. +# load +# Load a package locale without set it (lazy loading from mcget). +# Returns the normalized set locale. +# The default locale is taken, if locale is not given. # get # Get the locale valid for this package. # isset @@ -492,7 +505,7 @@ proc msgcat::mcloadedlocales {subcommand} { # Results: # Empty string, if not stated differently for the subcommand -proc msgcat::mcpackagelocale {subcommand {locale ""}} { +proc msgcat::mcpackagelocale {subcommand {locale ""} {ns ""}} { # todo: implement using an ensemble variable Loclist variable LoadedLocales @@ -512,7 +525,9 @@ proc msgcat::mcpackagelocale {subcommand {locale ""}} { } set locale [string tolower $locale] } - set ns [uplevel 1 {::namespace current}] + if {$ns eq ""} { + set ns [uplevel 1 {::namespace current}] + } switch -exact -- $subcommand { get { return [lindex [PackagePreferences $ns] 0] } @@ -520,7 +535,7 @@ proc msgcat::mcpackagelocale {subcommand {locale ""}} { loaded { return [PackageLocales $ns] } present { return [expr {$locale in [PackageLocales $ns]} ]} isset { return [dict exists $PackageConfig loclist $ns] } - set { # set a package locale or add a package locale + set - load { # set a package locale or add a package locale # Copy the default locale if no package locale set so far if {![dict exists $PackageConfig loclist $ns]} { @@ -530,18 +545,21 @@ proc msgcat::mcpackagelocale {subcommand {locale ""}} { # Check if changed set loclist [dict get $PackageConfig loclist $ns] - if {! [info exists locale] || $locale eq [lindex $loclist 0] } { + if {[llength [info level 0]] == 2 || $locale eq [lindex $loclist 0] } { return [lindex $loclist 0] } # Change loclist set loclist [GetPreferences $locale] set locale [lindex $loclist 0] - dict set PackageConfig loclist $ns $loclist - dict set PackageConfig locales $ns $locale $loclist + if {$subcommand eq {set}} { + # set loclist + dict set PackageConfig loclist $ns $loclist + } # load eventual missing locales set loadedLocales [dict get $PackageConfig loadedlocales $ns] + dict set PackageConfig locales $ns $locale $loclist if {$locale in $loadedLocales} { return $locale } set loadLocales [ListComplement $loadedLocales $loclist] dict set PackageConfig loadedlocales $ns\ @@ -899,6 +917,39 @@ proc msgcat::Load {ns locales {callbackonly 0}} { return $x } +# msgcat::Merge -- +# +# Merge message catalog dictionaries to one dictionary. +# +# Arguments: +# ns Namespace (equal package) to load the message catalog. +# locales List of locales to merge. +# +# Results: +# Returns the merged dictionary of message catalogs. +proc msgcat::Merge {ns locales} { + variable Merged + if {![catch { + set mrgcat [dict get $Merged $ns [set loc [lindex $locales 0]]] + }]} { + return $mrgcat + } + variable Msgs + # Merge sequential locales (in reverse order, e. g. {} -> en -> en_en): + if {[llength $locales] > 1} { + set mrgcat [msgcat::Merge $ns [lrange $locales 1 end]] + catch { + set mrgcat [dict merge $mrgcat [dict get $Msgs $ns $loc]] + } + } else { + catch { + set mrgcat [dict get $Msgs $ns $loc] + } + } + dict set Merged $ns $loc $mrgcat + return $mrgcat +} + # msgcat::Invoke -- # # Invoke a set of registered callbacks. @@ -971,6 +1022,7 @@ proc msgcat::Invoke {index arglist {ns ""} {resultname ""} {failerror 0}} { proc msgcat::mcset {locale src {dest ""}} { variable Msgs + variable Merged if {[llength [info level 0]] == 3} { ;# dest not specified set dest $src } @@ -980,6 +1032,7 @@ proc msgcat::mcset {locale src {dest ""}} { set locale [string tolower $locale] dict set Msgs $ns $locale $src $dest + dict unset Merged $ns return $dest } @@ -1019,6 +1072,7 @@ proc msgcat::mcflset {src {dest ""}} { proc msgcat::mcmset {locale pairs} { variable Msgs + variable Merged set length [llength $pairs] if {$length % 2} { @@ -1032,6 +1086,7 @@ proc msgcat::mcmset {locale pairs} { foreach {src dest} $pairs { dict set Msgs $ns $locale $src $dest } + dict unset Merged $ns return [expr {$length / 2}] } diff --git a/tests-perf/clock.perf.tcl b/tests-perf/clock.perf.tcl index fd24068..a005648 100644 --- a/tests-perf/clock.perf.tcl +++ b/tests-perf/clock.perf.tcl @@ -19,10 +19,11 @@ ## set testing defaults: set ::env(TCL_TZ) :CET -## warm-up (load clock.tcl, system zones, etc.): +## warm-up (load clock.tcl, system zones, locales, etc.): clock scan "" -gmt 1 clock scan "" clock scan "" -timezone :CET +clock scan "" -format "" -locale en ## ------------------------------------------ @@ -37,6 +38,20 @@ proc _test_get_commands {lst} { proc _test_out_total {} { upvar _ _ + if {![llength $_(ittm)]} { + puts "" + return + } + + set mintm 0x7fffffff + set maxtm 0 + set i 0 + foreach tm $_(ittm) { + if {$tm > $maxtm} {set maxtm $tm; set maxi $i} + if {$tm < $mintm} {set mintm $tm; set mini $i} + incr i + } + puts [string repeat ** 40] puts [format "Total %d cases in %.2f sec.:" [llength $_(itcnt)] [expr {[llength $_(itcnt)] * $_(reptime) / 1000.0}]] lset _(m) 0 [format %.6f [expr [join $_(ittm) +]]] @@ -48,6 +63,16 @@ proc _test_out_total {} { lset _(m) 2 [expr {[lindex $_(m) 2] / [llength $_(itcnt)]}] lset _(m) 4 [expr {[lindex $_(m) 2] * (1000 / $_(reptime))}] puts $_(m) + puts "Min:" + lset _(m) 0 [lindex $_(ittm) $mini] + lset _(m) 2 [lindex $_(itcnt) $mini] + lset _(m) 2 [lindex $_(itrate) $mini] + puts $_(m) + puts "Max:" + lset _(m) 0 [lindex $_(ittm) $maxi] + lset _(m) 2 [lindex $_(itcnt) $maxi] + lset _(m) 2 [lindex $_(itrate) $maxi] + puts $_(m) puts [string repeat ** 40] puts "" } @@ -123,8 +148,10 @@ proc test-scan {{reptime 1000}} { # Scan : century, lookup table month {clock scan {1970 Jan 2} -format {%C%y %b %d} -locale en -gmt 1} - # Scan : century, lookup table month and day + # Scan : century, lookup table month and day (both entries are first) {clock scan {1970 Jan 02} -format {%C%y %b %Od} -locale en -gmt 1} + # Scan : century, lookup table month and day (list scan: entries with position 12 / 31) + {clock scan {2016 Dec 31} -format {%C%y %b %Od} -locale en -gmt 1} break diff --git a/tests/clock.test b/tests/clock.test index 9e3dbd7..22b5bc1 100644 --- a/tests/clock.test +++ b/tests/clock.test @@ -18553,12 +18553,12 @@ test clock-6.8 {input of seconds} { } 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 {requested date 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 {requested date too large to represent} {CLOCK dateTooLarge}} test clock-6.11 {input of seconds - two values} { clock scan {1 2} -format {%s %s} -gmt true |