summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorsebres <sebres@users.sourceforge.net>2017-01-10 23:02:02 (GMT)
committersebres <sebres@users.sourceforge.net>2017-01-10 23:02:02 (GMT)
commit77a54daa546c50756c2734f86b9e230f02bcc676 (patch)
tree797ec4ebde45a5be9332c0046013aadd9652e431
parenta46b1f79d3095b4e867148fee2cb94dfe84fab44 (diff)
downloadtcl-77a54daa546c50756c2734f86b9e230f02bcc676.zip
tcl-77a54daa546c50756c2734f86b9e230f02bcc676.tar.gz
tcl-77a54daa546c50756c2734f86b9e230f02bcc676.tar.bz2
"clock add" rewritten in C, using common functionality of "clock scan" (and freescan)...
test-performance.tcl: test cases extended to cover "clock add"
-rw-r--r--generic/tclClock.c735
-rw-r--r--generic/tclDate.h2
-rwxr-xr-xlibrary/clock.tcl328
-rw-r--r--library/init.tcl2
-rw-r--r--tests-perf/clock.perf.tcl39
-rw-r--r--tests/clock.test17
6 files changed, 559 insertions, 564 deletions
diff --git a/generic/tclClock.c b/generic/tclClock.c
index fc7c70c..791898b 100644
--- a/generic/tclClock.c
+++ b/generic/tclClock.c
@@ -116,9 +116,6 @@ static int ClockMicrosecondsObjCmd(
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[]);
@@ -128,10 +125,17 @@ static int ClockFormatObjCmd(
static int ClockScanObjCmd(
ClientData clientData, Tcl_Interp *interp,
int objc, Tcl_Obj *const objv[]);
+static int ClockScanCommit(
+ ClientData clientData, register DateInfo *info,
+ register ClockFmtScnCmdArgs *opts);
static int ClockFreeScan(
- ClientData clientData, Tcl_Interp *interp,
register DateInfo *info,
Tcl_Obj *strObj, ClockFmtScnCmdArgs *opts);
+static int ClockCalcRelTime(
+ register DateInfo *info, ClockFmtScnCmdArgs *opts);
+static int ClockAddObjCmd(
+ ClientData clientData, Tcl_Interp *interp,
+ int objc, Tcl_Obj *const objv[]);
static struct tm * ThreadSafeLocalTime(const time_t *);
static unsigned long TzsetGetEpoch(void);
static void TzsetIfNecessary(void);
@@ -151,6 +155,7 @@ struct ClockCommand {
};
static const struct ClockCommand clockCommands[] = {
+ { "add", ClockAddObjCmd },
{ "clicks", ClockClicksObjCmd },
{ "getenv", ClockGetenvObjCmd },
{ "microseconds", ClockMicrosecondsObjCmd },
@@ -166,7 +171,6 @@ static const struct ClockCommand clockCommands[] = {
ClockGetjuliandayfromerayearmonthdayObjCmd },
{ "GetJulianDayFromEraYearWeekDay",
ClockGetjuliandayfromerayearweekdayObjCmd },
- { "ParseFormatArgs", ClockParseformatargsObjCmd },
{ NULL, NULL }
};
@@ -763,7 +767,6 @@ ClockConfigureObjCmd(
Tcl_Obj *const objv[]) /* Parameter vector */
{
ClockClientData *dataPtr = clientData;
- Tcl_Obj **litPtr = dataPtr->literals;
static const char *const options[] = {
"-system-tz", "-setup-tz", "-default-locale",
@@ -814,7 +817,7 @@ ClockConfigureObjCmd(
Tcl_SetObjRef(dataPtr->LastSetupTimeZone, timezoneObj);
Tcl_UnsetObjRef(dataPtr->LastSetupTZData);
}
- if (timezoneObj == litPtr[LIT_GMT]) {
+ if (timezoneObj == dataPtr->literals[LIT_GMT]) {
optionIndex = CLOCK_SETUP_GMT;
} else if (timezoneObj == dataPtr->SystemTimeZone) {
optionIndex = CLOCK_SETUP_NOP;
@@ -1158,7 +1161,7 @@ ClockConvertlocaltoutcObjCmd(
"found in dictionary", -1));
return TCL_ERROR;
}
- if ((Tcl_GetWideIntFromObj(interp, secondsObj,
+ if ((TclGetWideIntFromObj(interp, secondsObj,
&fields.localSeconds) != TCL_OK)
|| (TclGetIntFromObj(interp, objv[3], &changeover) != TCL_OK)
|| ConvertLocalToUTC(clientData, interp, &fields, objv[2], changeover)) {
@@ -1238,7 +1241,7 @@ ClockGetdatefieldsObjCmd(
Tcl_WrongNumArgs(interp, 1, objv, "seconds timezone changeover");
return TCL_ERROR;
}
- if (Tcl_GetWideIntFromObj(interp, objv[1], &fields.seconds) != TCL_OK
+ if (TclGetWideIntFromObj(interp, objv[1], &fields.seconds) != TCL_OK
|| TclGetIntFromObj(interp, objv[3], &changeover) != TCL_OK) {
return TCL_ERROR;
}
@@ -1762,7 +1765,7 @@ ConvertLocalToUTCUsingTable(
int corrOffset;
Tcl_WideInt backCompVal;
/* check DST-hole interval contains UTC time */
- Tcl_GetWideIntFromObj(NULL, cellv[0], &backCompVal);
+ TclGetWideIntFromObj(NULL, cellv[0], &backCompVal);
if ( fields->seconds >= backCompVal - fields->tzOffset
&& fields->seconds <= backCompVal + fields->tzOffset
) {
@@ -2148,7 +2151,7 @@ LookupLastTransition(
*/
if (Tcl_ListObjIndex(interp, rowv[0], 0, &compObj) != TCL_OK
- || Tcl_GetWideIntFromObj(interp, compObj, &compVal) != TCL_OK) {
+ || TclGetWideIntFromObj(interp, compObj, &compVal) != TCL_OK) {
return NULL;
}
@@ -2170,7 +2173,7 @@ LookupLastTransition(
int m = (l + u + 1) / 2;
if (Tcl_ListObjIndex(interp, rowv[m], 0, &compObj) != TCL_OK ||
- Tcl_GetWideIntFromObj(interp, compObj, &compVal) != TCL_OK) {
+ TclGetWideIntFromObj(interp, compObj, &compVal) != TCL_OK) {
return NULL;
}
if (tick >= compVal) {
@@ -2922,10 +2925,21 @@ ClockMicrosecondsObjCmd(
return TCL_OK;
}
+static inline void
+ClockInitFmtScnArgs(
+ ClientData clientData,
+ Tcl_Interp *interp,
+ ClockFmtScnCmdArgs *opts)
+{
+ memset(opts, 0, sizeof(*opts));
+ opts->clientData = clientData;
+ opts->interp = interp;
+}
+
/*
*-----------------------------------------------------------------------------
*
- * _ClockParseFmtScnArgs --
+ * ClockParseFmtScnArgs --
*
* Parses the arguments for [clock scan] and [clock format].
*
@@ -2936,92 +2950,116 @@ ClockMicrosecondsObjCmd(
*-----------------------------------------------------------------------------
*/
+#define CLC_FMT_ARGS (0)
+#define CLC_SCN_ARGS (1 << 0)
+#define CLC_ADD_ARGS (1 << 1)
+
static int
-_ClockParseFmtScnArgs(
- ClientData clientData, /* Client data containing literal pool */
- Tcl_Interp *interp, /* Tcl interpreter */
+ClockParseFmtScnArgs(
+ register
+ 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 */
- ClockFmtScnCmdArgs *opts, /* Result vector: format, locale, timezone... */
- int forScan /* Flag to differentiate between format and scan */
+ int flags /* Flags, differentiates between format, scan, add */
) {
- ClockClientData *dataPtr = clientData;
- Tcl_Obj **litPtr = dataPtr->literals;
+ Tcl_Interp *interp = opts->interp;
+ ClockClientData *dataPtr = opts->clientData;
int gmtFlag = 0;
- static const char *const options[2][6] = {
- { /* Format command line options */
- "-format", "-gmt", "-locale",
- "-timezone", NULL },
- { /* Scan command line options */
+ static const char *const options[] = {
"-format", "-gmt", "-locale",
- "-timezone", "-base", NULL }
+ "-timezone", "-base", NULL
};
enum optionInd {
- CLOCK_FORMAT_FORMAT, CLOCK_FORMAT_GMT, CLOCK_FORMAT_LOCALE,
- CLOCK_FORMAT_TIMEZONE, CLOCK_FORMAT_BASE
+ CLC_ARGS_FORMAT, CLC_ARGS_GMT, CLC_ARGS_LOCALE,
+ CLC_ARGS_TIMEZONE, CLC_ARGS_BASE
};
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 */
+
+ /* clock value (as current base) */
+ if ( !(flags & (CLC_SCN_ARGS)) ) {
+ opts->baseObj = objv[1];
+ saw |= (1 << CLC_ARGS_BASE);
+ }
/*
* Extract values for the keywords.
*/
- memset(opts, 0, sizeof(*opts));
for (i = 2; i < objc; i+=2) {
- if (Tcl_GetIndexFromObj(interp, objv[i], options[forScan],
+ /* 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) {
- Tcl_SetErrorCode(interp, "CLOCK", "badOption",
- Tcl_GetString(objv[i]), NULL);
- return TCL_ERROR;
+ goto badOption;
+ }
+ /* if already specified */
+ if (saw & (1 << optionIndex)) {
+ Tcl_SetObjResult(interp, Tcl_ObjPrintf(
+ "bad option \"%s\": doubly present",
+ TclGetString(objv[i]))
+ );
+ goto badOption;
}
switch (optionIndex) {
- case CLOCK_FORMAT_FORMAT:
+ 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:
+ case CLC_ARGS_LOCALE:
opts->localeObj = objv[i+1];
break;
- case CLOCK_FORMAT_TIMEZONE:
+ case CLC_ARGS_TIMEZONE:
opts->timezoneObj = objv[i+1];
break;
- case CLOCK_FORMAT_BASE:
+ case CLC_ARGS_BASE:
+ if ( !(flags & (CLC_SCN_ARGS)) ) {
+ goto badOptionMsg;
+ }
opts->baseObj = objv[i+1];
break;
}
- saw |= 1 << optionIndex;
+ saw |= (1 << optionIndex);
}
/*
* Check options.
*/
- if ((saw & (1 << CLOCK_FORMAT_GMT))
- && (saw & (1 << CLOCK_FORMAT_TIMEZONE))) {
+ if ((saw & (1 << CLC_ARGS_GMT))
+ && (saw & (1 << CLC_ARGS_TIMEZONE))) {
Tcl_SetResult(interp, "cannot use -gmt and -timezone in same call", TCL_STATIC);
Tcl_SetErrorCode(interp, "CLOCK", "gmtWithTimezone", NULL);
return TCL_ERROR;
}
if (gmtFlag) {
- opts->timezoneObj = litPtr[LIT_GMT];
+ opts->timezoneObj = dataPtr->literals[LIT_GMT];
}
- opts->clientData = clientData;
- opts->interp = interp;
-
/* If time zone not specified use system time zone */
if ( opts->timezoneObj == NULL
|| TclGetString(opts->timezoneObj) == NULL
|| opts->timezoneObj->length == 0
) {
- opts->timezoneObj = ClockGetSystemTimeZone(clientData, interp);
+ opts->timezoneObj = ClockGetSystemTimeZone(opts->clientData, interp);
if (opts->timezoneObj == NULL) {
return TCL_ERROR;
}
@@ -3029,85 +3067,90 @@ _ClockParseFmtScnArgs(
/* Setup timezone (normalize object if needed and load TZ on demand) */
- opts->timezoneObj = ClockSetupTimeZone(clientData, interp, opts->timezoneObj);
+ opts->timezoneObj = ClockSetupTimeZone(opts->clientData, interp, opts->timezoneObj);
if (opts->timezoneObj == NULL) {
return TCL_ERROR;
}
- 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 for backward compatibility purposes.
- *
- *-----------------------------------------------------------------------------
- */
+ /* Base (by scan or add) or clock value (by format) */
-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 = clientData;
- Tcl_Obj **literals = dataPtr->literals;
- ClockFmtScnCmdArgs opts; /* Format, locale and timezone */
- Tcl_WideInt clockVal; /* Clock value - just used to parse. */
- int ret;
+ if (opts->baseObj != NULL) {
+ if (TclGetWideIntFromObj(NULL, opts->baseObj, &baseVal) != TCL_OK) {
- /*
- * Args consist of a time followed by keyword-value pairs.
- */
+ /* we accept "-now" as current date-time */
+ const char *const nowOpts[] = {
+ "-now", NULL
+ };
+ int idx;
+ if (Tcl_GetIndexFromObj(NULL, opts->baseObj, nowOpts, "seconds or -now",
+ TCL_EXACT, &idx) == TCL_OK
+ ) {
+ goto baseNow;
+ }
- 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;
- }
+ Tcl_SetObjResult(interp, Tcl_ObjPrintf(
+ "expected integer but got \"%s\"",
+ Tcl_GetString(opts->baseObj)));
+ Tcl_SetErrorCode(interp, "TCL", "VALUE", "INTEGER", NULL);
+ i = 1;
+ goto badOption;
+ }
+ /*
+ * seconds could be an unsigned number that overflowed. Make sure
+ * that it isn't.
+ */
- /*
- * Extract values for the keywords.
- */
+ if (opts->baseObj->typePtr == &tclBignumType) {
+ Tcl_SetObjResult(interp, dataPtr->literals[LIT_INTEGER_VALUE_TOO_LARGE]);
+ return TCL_ERROR;
+ }
- ret = _ClockParseFmtScnArgs(clientData, interp, objc, objv,
- &opts, 0);
- if (ret != TCL_OK) {
- return ret;
+ } else {
+
+baseNow:
+ {
+ Tcl_Time now;
+ Tcl_GetTime(&now);
+ baseVal = (Tcl_WideInt) now.sec;
+ }
}
/*
- * Check options.
+ * Extract year, month and day from the base time for the parser to use as
+ * defaults
*/
- if (Tcl_GetWideIntFromObj(interp, objv[1], &clockVal) != TCL_OK) {
- return TCL_ERROR;
+ /* check base fields already cached (by TZ, last-second cache) */
+ if ( dataPtr->lastBase.timezoneObj == opts->timezoneObj
+ && dataPtr->lastBase.Date.seconds == baseVal) {
+ 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);
}
- if (opts.formatObj == NULL)
- opts.formatObj = literals[LIT__DEFAULT_FORMAT];
- if (opts.localeObj == NULL)
- opts.localeObj = literals[LIT_C];
- if (opts.timezoneObj == NULL)
- opts.timezoneObj = literals[LIT__NIL];
- /*
- * Return options as a list.
- */
-
- Tcl_SetObjResult(interp, Tcl_NewListObj(3, (Tcl_Obj**)&opts.formatObj));
return TCL_OK;
+
+badOptionMsg:
+
+ Tcl_SetObjResult(interp, Tcl_ObjPrintf(
+ "bad option \"%s\": unexpected for command \"%s\"",
+ TclGetString(objv[i]), TclGetString(objv[0]))
+ );
+
+badOption:
+
+ Tcl_SetErrorCode(interp, "CLOCK", "badOption",
+ i < objc ? Tcl_GetString(objv[i]) : NULL, NULL);
+
+ return TCL_ERROR;
}
/*----------------------------------------------------------------------
@@ -3141,11 +3184,11 @@ ClockFormatObjCmd(
int ret;
ClockFmtScnCmdArgs opts; /* Format, locale, timezone and base */
- Tcl_WideInt clockVal; /* Time, expressed in seconds from the Epoch */
DateFormat dateFmt; /* Common structure used for formatting */
+ /* even number of arguments */
if ((objc & 1) == 1) {
- Tcl_WrongNumArgs(interp, 1, objv, "clockval "
+ Tcl_WrongNumArgs(interp, 1, objv, "clockval|-now "
"?-format string? "
"?-gmt boolean? "
"?-locale LOCALE? ?-timezone ZONE?");
@@ -3153,53 +3196,17 @@ ClockFormatObjCmd(
return TCL_ERROR;
}
- /*
- * Extract values for the keywords.
- */
-
- ret = _ClockParseFmtScnArgs(clientData, interp, objc, objv,
- &opts, 0);
- if (ret != TCL_OK) {
- return ret;
- }
-
- ret = TCL_ERROR;
-
- if (Tcl_GetWideIntFromObj(interp, objv[1], &clockVal) != TCL_OK) {
- return TCL_ERROR;
- }
-
- /*
- * seconds could be an unsigned number that overflowed. Make sure
- * that it isn't.
- */
-
- if (objv[1]->typePtr == &tclBignumType) {
- Tcl_SetObjResult(interp, dataPtr->literals[LIT_INTEGER_VALUE_TOO_LARGE]);
- return TCL_ERROR;
- }
-
memset(&dateFmt, 0, sizeof(dateFmt));
/*
- * Extract year, month and day from the base time for the parser to use as
- * defaults
+ * Extract values for the keywords.
*/
- /* check base fields already cached (by TZ, last-second cache) */
- if ( dataPtr->lastBase.timezoneObj == opts.timezoneObj
- && dataPtr->lastBase.Date.seconds == clockVal) {
- memcpy(&dateFmt.date, &dataPtr->lastBase.Date, ClockCacheableDateFieldsSize);
- } else {
- /* extact fields from base */
- dateFmt.date.seconds = clockVal;
- if (ClockGetDateFields(clientData, interp, &dateFmt.date, opts.timezoneObj,
- GREGORIAN_CHANGE_DATE) != TCL_OK) {
- goto done;
- }
- /* cache last base */
- memcpy(&dataPtr->lastBase.Date, &dateFmt.date, ClockCacheableDateFieldsSize);
- Tcl_SetObjRef(dataPtr->lastBase.timezoneObj, opts.timezoneObj);
+ ClockInitFmtScnArgs(clientData, interp, &opts);
+ ret = ClockParseFmtScnArgs(&opts, &dateFmt.date, objc, objv,
+ CLC_FMT_ARGS);
+ if (ret != TCL_OK) {
+ goto done;
}
/* Default format */
@@ -3249,14 +3256,12 @@ ClockScanObjCmd(
int objc, /* Parameter count */
Tcl_Obj *const objv[]) /* Parameter values */
{
- ClockClientData *dataPtr = clientData;
-
int ret;
ClockFmtScnCmdArgs opts; /* Format, locale, timezone and base */
- Tcl_WideInt baseVal; /* Base time, expressed in seconds from the Epoch */
DateInfo yy; /* Common structure used for parsing */
DateInfo *info = &yy;
+ /* even number of arguments */
if ((objc & 1) == 1) {
Tcl_WrongNumArgs(interp, 1, objv, "string "
"?-base seconds? "
@@ -3267,59 +3272,17 @@ ClockScanObjCmd(
return TCL_ERROR;
}
+ ClockInitDateInfo(&yy);
+
/*
* Extract values for the keywords.
*/
- ret = _ClockParseFmtScnArgs(clientData, interp, objc, objv,
- &opts, 1);
+ ClockInitFmtScnArgs(clientData, interp, &opts);
+ ret = ClockParseFmtScnArgs(&opts, &yy.date, objc, objv,
+ CLC_SCN_ARGS);
if (ret != TCL_OK) {
- return ret;
- }
-
- ret = TCL_ERROR;
-
- if (opts.baseObj != NULL) {
- if (Tcl_GetWideIntFromObj(interp, opts.baseObj, &baseVal) != TCL_OK) {
- return TCL_ERROR;
- }
- /*
- * seconds could be an unsigned number that overflowed. Make sure
- * that it isn't.
- */
-
- if (opts.baseObj->typePtr == &tclBignumType) {
- Tcl_SetObjResult(interp, dataPtr->literals[LIT_INTEGER_VALUE_TOO_LARGE]);
- return TCL_ERROR;
- }
-
- } else {
- Tcl_Time now;
- Tcl_GetTime(&now);
- baseVal = (Tcl_WideInt) now.sec;
- }
-
- ClockInitDateInfo(info);
-
- /*
- * 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) {
- memcpy(&yydate, &dataPtr->lastBase.Date, ClockCacheableDateFieldsSize);
- } else {
- /* extact fields from base */
- yydate.seconds = baseVal;
- if (ClockGetDateFields(clientData, interp, &yydate, opts.timezoneObj,
- GREGORIAN_CHANGE_DATE) != TCL_OK) {
- goto done;
- }
- /* cache last base */
- memcpy(&dataPtr->lastBase.Date, &yydate, ClockCacheableDateFieldsSize);
- Tcl_SetObjRef(dataPtr->lastBase.timezoneObj, opts.timezoneObj);
+ goto done;
}
/* seconds are in localSeconds (relative base date), so reset time here */
@@ -3336,18 +3299,54 @@ ClockScanObjCmd(
Tcl_SetErrorCode(interp, "CLOCK", "flagWithLegacyFormat", NULL);
return TCL_ERROR;
}
- ret = ClockFreeScan(clientData, interp, info, objv[1], &opts);
+ ret = ClockFreeScan(&yy, objv[1], &opts);
}
else {
/* Use compiled version of Scan - */
- ret = ClockScan(info, objv[1], &opts);
+ ret = ClockScan(&yy, objv[1], &opts);
+ }
+
+ /* Convert date info structure into UTC seconds */
+
+ if (ret == TCL_OK) {
+ ret = ClockScanCommit(clientData, &yy, &opts);
}
+done:
+
+ Tcl_UnsetObjRef(yy.date.tzName);
+
if (ret != TCL_OK) {
- goto done;
+ 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(
+ ClientData clientData, /* Client data containing literal pool */
+ register DateInfo *info, /* Clock scan info structure */
+ register
+ 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_ISO8601)) {
@@ -3362,13 +3361,12 @@ ClockScanObjCmd(
}
/* some overflow checks, if not extended */
- if (!(opts.flags & CLF_EXTENDED)) {
+ if (!(opts->flags & CLF_EXTENDED)) {
if (yydate.julianDay > 5373484) {
- Tcl_SetObjResult(interp, Tcl_NewStringObj(
+ Tcl_SetObjResult(opts->interp, Tcl_NewStringObj(
"requested date too large to represent", -1));
- Tcl_SetErrorCode(interp, "CLOCK", "dateTooLarge", NULL);
- ret = TCL_ERROR;
- goto done;
+ Tcl_SetErrorCode(opts->interp, "CLOCK", "dateTooLarge", NULL);
+ return TCL_ERROR;
}
}
@@ -3382,9 +3380,9 @@ ClockScanObjCmd(
}
if (info->flags & (CLF_ASSEMBLE_SECONDS|CLF_ASSEMBLE_JULIANDAY|CLF_LOCALSEC)) {
- if (ConvertLocalToUTC(clientData, interp, &yydate, opts.timezoneObj,
+ if (ConvertLocalToUTC(clientData, opts->interp, &yydate, opts->timezoneObj,
GREGORIAN_CHANGE_DATE) != TCL_OK) {
- goto done;
+ return TCL_ERROR;
}
}
@@ -3392,17 +3390,6 @@ ClockScanObjCmd(
yydate.seconds += yyRelSeconds;
- ret = TCL_OK;
-
-done:
-
- Tcl_UnsetObjRef(yydate.tzName);
-
- if (ret != TCL_OK) {
- return ret;
- }
-
- Tcl_SetObjResult(interp, Tcl_NewWideIntObj(yydate.seconds));
return TCL_OK;
}
@@ -3423,8 +3410,6 @@ done:
int
ClockFreeScan(
- ClientData clientData, /* Client data containing literal pool */
- Tcl_Interp *interp, /* Tcl interpreter */
register
DateInfo *info, /* Date fields used for parsing & converting
* simultaneously a yy-parse structure of the
@@ -3432,7 +3417,8 @@ ClockFreeScan(
Tcl_Obj *strObj, /* String containing the time to scan */
ClockFmtScnCmdArgs *opts) /* Command options */
{
- ClockClientData *dataPtr = clientData;
+ Tcl_Interp *interp = opts->interp;
+ ClockClientData *dataPtr = opts->clientData;
int ret = TCL_ERROR;
@@ -3487,7 +3473,7 @@ ClockFreeScan(
60 * minEast + 3600 * dstFlag);
Tcl_IncrRefCount(tzObjStor);
- opts->timezoneObj = ClockSetupTimeZone(clientData, interp, tzObjStor);
+ opts->timezoneObj = ClockSetupTimeZone(dataPtr, interp, tzObjStor);
Tcl_DecrRefCount(tzObjStor);
if (opts->timezoneObj == NULL) {
@@ -3531,6 +3517,39 @@ ClockFreeScan(
* Do relative times
*/
+ ret = ClockCalcRelTime(info, opts);
+
+ /* 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(
+ register
+ DateInfo *info, /* Date fields used for converting */
+ ClockFmtScnCmdArgs *opts) /* Command options */
+{
+ /*
+ * Because some calculations require in-between conversion of the
+ * julian day, we can repeat this processing multiple times
+ */
repeat_rel:
if (yyHaveRel) {
@@ -3667,13 +3686,267 @@ repeat_rel:
info->flags |= CLF_ASSEMBLE_DATE|CLF_ASSEMBLE_SECONDS;
}
- /* Free scanning completed - date ready */
+ 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(
+ register int dayOfWeek,
+ register int offs)
+{
+ register 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 */
+ {
+ register 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 */
+{
+ ClockClientData *dataPtr = 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;
- ret = TCL_OK;
+ /* even number of arguments */
+ if ((objc & 1) == 1) {
+ Tcl_WrongNumArgs(interp, 1, objv, "clockval|-now ?number units?..."
+ "?-gmt boolean? "
+ "?-locale LOCALE? ?-timezone ZONE?");
+ Tcl_SetErrorCode(interp, "CLOCK", "wrongNumArgs", 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);
+ if (ret != TCL_OK) {
+ goto done;
+ }
+
+ /* time together as seconds of the day */
+ 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) */
+ if (TclGetWideIntFromObj(NULL, objv[i], &offs) != TCL_OK) {
+ continue;
+ }
+ if (objv[i]->typePtr == &tclBignumType) {
+ Tcl_SetObjResult(interp, dataPtr->literals[LIT_INTEGER_VALUE_TOO_LARGE]);
+ goto done;
+ }
+ /* get unit */
+ if (Tcl_GetIndexFromObj(interp, objv[i+1], units, "unit", 0,
+ &unitIndex) != TCL_OK) {
+ 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 ( yyHaveRel
+ && ( 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, &opts) != TCL_OK) {
+ goto done;
+ }
+ }
+
+ /* process increment by offset + unit */
+ yyHaveRel++;
+ switch (unitIndex) {
+ case CLC_ADD_YEARS:
+ yyRelMonth += offs * 12;
+ break;
+ case CLC_ADD_MONTHS:
+ yyRelMonth += offs;
+ break;
+ case CLC_ADD_WEEK:
+ case CLC_ADD_WEEKS:
+ yyRelDay += offs * 7;
+ break;
+ case CLC_ADD_DAYS:
+ yyRelDay += offs;
+ break;
+ case CLC_ADD_WEEKDAYS:
+ /* add number of week days (skipping Saturdays and Sundays)
+ * to a relative days value. */
+ offs = ClockWeekdaysOffs(yy.date.dayOfWeek, offs);
+ yyRelDay += offs;
+ break;
+ case CLC_ADD_HOURS:
+ yyRelSeconds += offs * 60 * 60;
+ break;
+ case CLC_ADD_MINUTES:
+ yyRelSeconds += offs * 60;
+ break;
+ case CLC_ADD_SECONDS:
+ yyRelSeconds += offs;
+ break;
+ }
+ }
+
+ /*
+ * Do relative times (if not yet already processed interim):
+ */
+
+ if (yyHaveRel) {
+ if (ClockCalcRelTime(info, &opts) != TCL_OK) {
+ goto done;
+ }
+ }
+
+ /* Convert date info structure into UTC seconds */
+
+ ret = ClockScanCommit(clientData, &yy, &opts);
done:
- return ret;
+ Tcl_UnsetObjRef(yy.date.tzName);
+
+ if (ret != TCL_OK) {
+ return ret;
+ }
+
+ Tcl_SetObjResult(interp, Tcl_NewWideIntObj(yy.date.seconds));
+ return TCL_OK;
}
/*----------------------------------------------------------------------
diff --git a/generic/tclDate.h b/generic/tclDate.h
index 1519842..e614f9d 100644
--- a/generic/tclDate.h
+++ b/generic/tclDate.h
@@ -264,7 +264,7 @@ typedef struct ClockFmtScnCmdArgs {
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) */
+ 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*/
diff --git a/library/clock.tcl b/library/clock.tcl
index 3caa270..ba4676b 100755
--- a/library/clock.tcl
+++ b/library/clock.tcl
@@ -1948,334 +1948,6 @@ proc ::tcl::clock::WeekdayOnOrBefore { weekday j } {
#----------------------------------------------------------------------
#
-# 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 "expected integer but got \"$clockval\""
- }
-
- set offsets {}
- set gmt 0
- set locale c
- if {[set timezone [configure -system-tz]] eq ""} {
- 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 gmt $b
- }
- -l - -lo - -loc - -loca - -local - -locale {
- set locale [string tolower $b]
- }
- -t - -ti - -tim - -time - -timez - -timezo - -timezon -
- -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 { ![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 {set timezone [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]
- }
-
- weekdays - weekday {
- set clockval [AddWeekDays $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 $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 {}] $timezone \
- $changeover]
-
- return [dict get $date seconds]
-
-}
-
-#----------------------------------------------------------------------
-#
-# AddWeekDays --
-#
-# Add a given number of week days (skipping Saturdays and Sundays)
-# 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::AddWeekDays { days clockval timezone changeover } {
-
- if {$days == 0} {
- return $clockval
- }
-
- set day [format $clockval -format %u]
-
- set weeks [expr {$days / 5}]
- set rdays [expr {$days % 5}]
- set toAdd [expr {7 * $weeks + $rdays}]
- set resDay [expr {$day + ($toAdd % 7)}]
-
- # Adjust if we start from a weekend
- if {$day > 5} {
- set adj [expr {5 - $day}]
- incr toAdd $adj
- incr resDay $adj
- }
-
- # Adjust if we end up on a weekend
- if {$resDay > 5} {
- incr toAdd 2
- }
-
- AddDays $toAdd $clockval $timezone $changeover
-}
-
-#----------------------------------------------------------------------
-#
-# 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 $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 {}] $timezone \
- $changeover]
-
- return [dict get $date seconds]
-
-}
-
-#----------------------------------------------------------------------
-#
# ChangeCurrentLocale --
#
# The global locale was changed within msgcat.
diff --git a/library/init.tcl b/library/init.tcl
index f1f1bb4..405a400 100644
--- a/library/init.tcl
+++ b/library/init.tcl
@@ -178,7 +178,7 @@ if {[interp issafe]} {
# Auto-loading stubs for 'clock.tcl'
- foreach cmd {add mcget LocalizeFormat SetupTimeZone GetSystemTimeZone} {
+ foreach cmd {mcget LocalizeFormat SetupTimeZone GetSystemTimeZone} {
proc ::tcl::clock::$cmd args {
variable TclLibDir
source -encoding utf-8 [file join $TclLibDir clock.tcl]
diff --git a/tests-perf/clock.perf.tcl b/tests-perf/clock.perf.tcl
index 16664b2..733db1a 100644
--- a/tests-perf/clock.perf.tcl
+++ b/tests-perf/clock.perf.tcl
@@ -318,6 +318,44 @@ proc test-freescan {{reptime 1000}} {
} {puts [clock format $_(r) -locale en]}
}
+proc test-add {{reptime 1000}} {
+ _test_run $reptime {
+ # Add : years
+ {clock add 1246379415 5 years -gmt 1}
+ # Add : months
+ {clock add 1246379415 18 months -gmt 1}
+ # Add : weeks
+ {clock add 1246379415 20 weeks -gmt 1}
+ # Add : days
+ {clock add 1246379415 385 days -gmt 1}
+ # Add : weekdays
+ {clock add 1246379415 3 weekdays -gmt 1}
+
+ # Add : hours
+ {clock add 1246379415 5 hours -gmt 1}
+ # Add : minutes
+ {clock add 1246379415 55 minutes -gmt 1}
+ # Add : seconds
+ {clock add 1246379415 100 seconds -gmt 1}
+
+ # Add : +/- in gmt
+ {clock add 1246379415 -5 years +21 months -20 weeks +386 days -19 hours +30 minutes -10 seconds -gmt 1}
+ # Add : +/- in system timezone
+ {clock add 1246379415 -5 years +21 months -20 weeks +386 days -19 hours +30 minutes -10 seconds -timezone :CET}
+
+ # Add : gmt
+ {clock add 1246379415 -5 years 18 months 366 days 5 hours 30 minutes 10 seconds -gmt 1}
+ # Add : system timezone
+ {clock add 1246379415 -5 years 18 months 366 days 5 hours 30 minutes 10 seconds -timezone :CET}
+
+ # Add : all in gmt
+ {clock add 1246379415 4 years 18 months 50 weeks 378 days 3 weekdays 5 hours 30 minutes 10 seconds -gmt 1}
+ # Add : all in system timezone
+ {clock add 1246379415 4 years 18 months 50 weeks 378 days 3 weekdays 5 hours 30 minutes 10 seconds -timezone :CET}
+
+ } {puts [clock format $_(r) -locale en]}
+}
+
proc test-other {{reptime 1000}} {
_test_run $reptime {
# Bad zone
@@ -338,6 +376,7 @@ proc test {{reptime 1000}} {
test-format $reptime
test-scan $reptime
test-freescan $reptime
+ test-add $reptime
test-other $reptime
puts \n**OK**
diff --git a/tests/clock.test b/tests/clock.test
index d9c491a..b540024 100644
--- a/tests/clock.test
+++ b/tests/clock.test
@@ -254,7 +254,7 @@ proc ::testClock::registry { cmd path key } {
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}}
+} {1 {wrong # args: should be "clock format clockval|-now ?-format string? ?-gmt boolean? ?-locale LOCALE? ?-timezone ZONE?"} {CLOCK wrongNumArgs}}
test clock-1.1 "clock format - bad time" {
list [catch {clock format foo} msg] $msg
@@ -270,10 +270,11 @@ test clock-1.3 "clock format - empty val" {
test clock-1.4 "clock format - bad flag" {*}{
-body {
- list [catch {clock format 0 -oops badflag} msg] $msg $::errorCode
+ # range error message for possible extensions:
+ list [catch {clock format 0 -oops badflag} msg] [string range $msg 0 60] $::errorCode
}
-match glob
- -result {1 {bad option "-oops": must be -format, -gmt, -locale, or -timezone} {CLOCK badOption -oops}}
+ -result {1 {bad option "-oops": must be -format, -gmt, -locale, -timezone} {CLOCK badOption -oops}}
}
test clock-1.5 "clock format - bad timezone" {
@@ -288,6 +289,16 @@ 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