summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--generic/tclClock.c394
-rw-r--r--generic/tclClockFmt.c220
-rw-r--r--generic/tclDate.h84
-rwxr-xr-xlibrary/clock.tcl13
-rw-r--r--tests-perf/clock.perf.tcl50
-rw-r--r--tests/clock.test87
6 files changed, 638 insertions, 210 deletions
diff --git a/generic/tclClock.c b/generic/tclClock.c
index 2e7b854..7cc5d86 100644
--- a/generic/tclClock.c
+++ b/generic/tclClock.c
@@ -26,22 +26,6 @@
#endif
/*
- * 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 */
-
-/*
* Table of the days in each month, leap and common years
*/
@@ -72,8 +56,6 @@ typedef enum ClockLiteral {
LIT_MONTH,
LIT_SECONDS, LIT_TZNAME, LIT_TZOFFSET,
LIT_YEAR,
- LIT_CURRENTYEARCENTURY,
- LIT_YEAROFCENTURYSWITCH,
LIT_TZDATA,
LIT_GETSYSTEMTIMEZONE,
LIT_SETUPTIMEZONE,
@@ -96,8 +78,6 @@ static const char *const Literals[] = {
"month",
"seconds", "tzName", "tzOffset",
"year",
- "::tcl::clock::CurrentYearCentury",
- "::tcl::clock::YearOfCenturySwitch",
"::tcl::clock::TZData",
"::tcl::clock::GetSystemTimeZone",
"::tcl::clock::SetupTimeZone",
@@ -106,41 +86,6 @@ static const char *const Literals[] = {
#endif
};
-#define CurrentYearCentury 2000
-
-/*
- * Structure containing the client data for [clock]
- */
-
-typedef struct ClockClientData {
- size_t refCount; /* Number of live references. */
- Tcl_Obj **literals; /* Pool of object literals. */
- /* Cache for current clock parameters, imparted via "configure" */
- unsigned long LastTZEpoch;
- int currentYearCentury;
- int yearOfCenturySwitch;
- Tcl_Obj *SystemTimeZone;
- Tcl_Obj *SystemSetupTZData;
- Tcl_Obj *GMTSetupTimeZone;
- Tcl_Obj *GMTSetupTZData;
- Tcl_Obj *AnySetupTimeZone;
- Tcl_Obj *AnySetupTZData;
- Tcl_Obj *LastUnnormSetupTimeZone;
- Tcl_Obj *LastSetupTimeZone;
- Tcl_Obj *LastSetupTZData;
- /* Cache for last base (fast convert if base/tz not changed) */
- Tcl_Obj *lastBaseTimeZone;
- TclDateFields lastBaseDate;
- /*
- /* [SB] TODO: back-port (from tclSE) the same date caching ...
- * Cache for last date (fast convert if date parsed was the same) * /
- struct {
- DateInfo yy;
- TclDateFields date;
- } lastDate;
- */
-} ClockClientData;
-
static const char *const eras[] = { "CE", "BCE", NULL };
/*
@@ -161,13 +106,13 @@ TCL_DECLARE_MUTEX(clockMutex)
* Function prototypes for local procedures in this file:
*/
-static int ConvertUTCToLocal(Tcl_Interp *,
+static int ConvertUTCToLocal(ClientData clientData, Tcl_Interp *,
TclDateFields *, Tcl_Obj *, int);
static int ConvertUTCToLocalUsingTable(Tcl_Interp *,
TclDateFields *, int, Tcl_Obj *const[]);
static int ConvertUTCToLocalUsingC(Tcl_Interp *,
TclDateFields *, int);
-static int ConvertLocalToUTC(Tcl_Interp *,
+static int ConvertLocalToUTC(ClientData clientData, Tcl_Interp *,
TclDateFields *, Tcl_Obj *, int);
static int ConvertLocalToUTCUsingTable(Tcl_Interp *,
TclDateFields *, int, Tcl_Obj *const[]);
@@ -191,9 +136,9 @@ static int ClockConvertlocaltoutcObjCmd(
ClientData clientData, Tcl_Interp *interp,
int objc, Tcl_Obj *const objv[]);
-static int ClockGetDateFields(Tcl_Interp *interp,
- TclDateFields *fields, Tcl_Obj *tzdata,
- int changeover);
+static int ClockGetDateFields(ClientData clientData,
+ Tcl_Interp *interp, TclDateFields *fields,
+ Tcl_Obj *tzdata, int changeover);
static int ClockGetdatefieldsObjCmd(
ClientData clientData, Tcl_Interp *interp,
int objc, Tcl_Obj *const objv[]);
@@ -330,8 +275,8 @@ TclClockInit(
Tcl_IncrRefCount(data->literals[i]);
}
data->LastTZEpoch = 0;
- data->currentYearCentury = -1;
- data->yearOfCenturySwitch = -1;
+ data->currentYearCentury = ClockDefaultYearCentury;
+ data->yearOfCenturySwitch = ClockDefaultCenturySwitch;
data->SystemTimeZone = NULL;
data->SystemSetupTZData = NULL;
data->GMTSetupTimeZone = NULL;
@@ -342,7 +287,10 @@ TclClockInit(
data->LastSetupTimeZone = NULL;
data->LastSetupTZData = NULL;
- data->lastBaseTimeZone = NULL;
+ data->lastBase.TimeZone = NULL;
+ data->UTC2Local.tzData = NULL;
+ data->UTC2Local.tzName = NULL;
+ data->Local2UTC.tzData = NULL;
/*
* Install the commands.
@@ -377,8 +325,6 @@ ClockConfigureClear(
ClockClientData *data)
{
data->LastTZEpoch = 0;
- data->currentYearCentury = -1;
- data->yearOfCenturySwitch = -1;
Tcl_UnsetObjRef(data->SystemTimeZone);
Tcl_UnsetObjRef(data->SystemSetupTZData);
Tcl_UnsetObjRef(data->GMTSetupTimeZone);
@@ -388,6 +334,11 @@ ClockConfigureClear(
Tcl_UnsetObjRef(data->LastUnnormSetupTimeZone);
Tcl_UnsetObjRef(data->LastSetupTimeZone);
Tcl_UnsetObjRef(data->LastSetupTZData);
+
+ Tcl_UnsetObjRef(data->lastBase.TimeZone);
+ Tcl_UnsetObjRef(data->UTC2Local.tzData);
+ Tcl_UnsetObjRef(data->UTC2Local.tzName);
+ Tcl_UnsetObjRef(data->Local2UTC.tzData);
}
static void
@@ -397,7 +348,8 @@ ClockDeleteCmdProc(
ClockClientData *data = clientData;
int i;
- if (data->refCount-- <= 1) {
+ data->refCount--;
+ if (data->refCount == 0) {
for (i = 0; i < LIT__END; ++i) {
Tcl_DecrRefCount(data->literals[i]);
}
@@ -471,10 +423,12 @@ ClockConfigureObjCmd(
static const char *const options[] = {
"-system-tz", "-setup-tz", "-clear",
+ "-year-century", "-century-switch",
NULL
};
enum optionInd {
CLOCK_SYSTEM_TZ, CLOCK_SETUP_TZ, CLOCK_CLEAR_CACHE,
+ CLOCK_YEAR_CENTURY, CLOCK_CENTURY_SWITCH,
CLOCK_SETUP_GMT, CLOCK_SETUP_NOP
};
int optionIndex; /* Index of an option. */
@@ -542,6 +496,32 @@ ClockConfigureObjCmd(
Tcl_SetObjResult(interp, dataPtr->LastSetupTimeZone);
}
break;
+ case CLOCK_YEAR_CENTURY:
+ if (i+1 < objc) {
+ int year;
+ if (TclGetIntFromObj(interp, objv[i+1], &year) != TCL_OK) {
+ return TCL_ERROR;
+ }
+ dataPtr->currentYearCentury = year;
+ Tcl_SetObjResult(interp, objv[i+1]);
+ continue;
+ }
+ Tcl_SetObjResult(interp,
+ Tcl_NewIntObj(dataPtr->currentYearCentury));
+ break;
+ case CLOCK_CENTURY_SWITCH:
+ if (i+1 < objc) {
+ int year;
+ if (TclGetIntFromObj(interp, objv[i+1], &year) != TCL_OK) {
+ return TCL_ERROR;
+ }
+ dataPtr->yearOfCenturySwitch = year;
+ Tcl_SetObjResult(interp, objv[i+1]);
+ continue;
+ }
+ Tcl_SetObjResult(interp,
+ Tcl_NewIntObj(dataPtr->yearOfCenturySwitch));
+ break;
case CLOCK_CLEAR_CACHE:
ClockConfigureClear(dataPtr);
break;
@@ -577,14 +557,16 @@ ClockGetTZData(
/* simple caching, because almost used the tz-data of last timezone
*/
if (timezoneObj == dataPtr->SystemTimeZone) {
- if (dataPtr->SystemSetupTZData != NULL)
+ if (dataPtr->SystemSetupTZData != NULL) {
return dataPtr->SystemSetupTZData;
+ }
out = &dataPtr->SystemSetupTZData;
}
else
if (timezoneObj == dataPtr->GMTSetupTimeZone) {
- if (dataPtr->GMTSetupTZData != NULL)
+ if (dataPtr->GMTSetupTZData != NULL) {
return dataPtr->GMTSetupTZData;
+ }
out = &dataPtr->GMTSetupTZData;
}
else
@@ -602,11 +584,11 @@ ClockGetTZData(
if (out != NULL) {
Tcl_SetObjRef(*out, ret);
}
- Tcl_SetObjRef(dataPtr->LastSetupTZData, ret);
+ Tcl_SetObjRef(dataPtr->LastSetupTZData, ret);
if (dataPtr->LastSetupTimeZone != timezoneObj) {
Tcl_SetObjRef(dataPtr->LastSetupTimeZone, timezoneObj);
Tcl_UnsetObjRef(dataPtr->LastUnnormSetupTimeZone);
- }
+ }
return ret;
}
/*
@@ -710,49 +692,6 @@ ClockFormatNumericTimeZone(int z) {
/*
*----------------------------------------------------------------------
- * [SB] TODO: make constans cacheable (once per second, etc.) ...
- */
-inline int
-ClockCurrentYearCentury(
- ClientData clientData, /* Opaque pointer to literal pool, etc. */
- Tcl_Interp *interp) /* Tcl interpreter */
-{
- ClockClientData *dataPtr = clientData;
- Tcl_Obj **literals = dataPtr->literals;
- int year = dataPtr->currentYearCentury;
-
- if (year == -1) {
- Tcl_Obj * yearObj;
- year = 2000;
- yearObj = Tcl_ObjGetVar2(interp,
- literals[LIT_CURRENTYEARCENTURY], NULL, TCL_LEAVE_ERR_MSG);
- Tcl_GetIntFromObj(NULL, yearObj, &year);
- dataPtr->currentYearCentury = year;
- }
- return year;
-}
-inline int
-ClockGetYearOfCenturySwitch(
- ClientData clientData, /* Opaque pointer to literal pool, etc. */
- Tcl_Interp *interp) /* Tcl interpreter */
-{
- ClockClientData *dataPtr = clientData;
- Tcl_Obj **literals = dataPtr->literals;
- int year = dataPtr->yearOfCenturySwitch;
-
- if (year == -1) {
- Tcl_Obj * yearObj;
- year = 37;
- yearObj = Tcl_ObjGetVar2(interp,
- literals[LIT_YEAROFCENTURYSWITCH], NULL, TCL_LEAVE_ERR_MSG);
- Tcl_GetIntFromObj(NULL, yearObj, &year);
- dataPtr->yearOfCenturySwitch = year;
- }
- return year;
-}
-
-/*
- *----------------------------------------------------------------------
*
* ClockConvertlocaltoutcObjCmd --
*
@@ -816,7 +755,7 @@ ClockConvertlocaltoutcObjCmd(
if ((Tcl_GetWideIntFromObj(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;
}
@@ -911,8 +850,8 @@ ClockGetdatefieldsObjCmd(
/* Extract fields */
- if (ClockGetDateFields(interp, &fields, objv[2], changeover)
- != TCL_OK) {
+ if (ClockGetDateFields(clientData, interp, &fields, objv[2],
+ changeover) != TCL_OK) {
return TCL_ERROR;
}
@@ -957,6 +896,7 @@ ClockGetdatefieldsObjCmd(
*/
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 */
@@ -967,7 +907,8 @@ ClockGetDateFields(
* Convert UTC time to local.
*/
- if (ConvertUTCToLocal(interp, fields, tzdata, changeover) != TCL_OK) {
+ if (ConvertUTCToLocal(clientData, interp, fields, tzdata,
+ changeover) != TCL_OK) {
return TCL_ERROR;
}
@@ -1221,15 +1162,33 @@ ClockGetjuliandayfromerayearweekdayObjCmd(
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 */
int changeover) /* Julian Day of the Gregorian transition */
{
+ ClockClientData *dataPtr = clientData;
int rowc; /* Number of rows in tzdata */
Tcl_Obj **rowv; /* Pointers to the rows */
/*
+ * Check cacheable conversion could be used
+ * (last-minute Local2UTC cache with the same TZ)
+ */
+ if ( tzdata == dataPtr->Local2UTC.tzData
+ && ( fields->localSeconds == dataPtr->Local2UTC.localSeconds
+ || fields->localSeconds / 60 == dataPtr->Local2UTC.localSeconds / 60
+ )
+ && changeover == dataPtr->Local2UTC.changeover
+ ) {
+ /* the same time zone and offset (UTC time inside the last minute) */
+ fields->tzOffset = dataPtr->Local2UTC.tzOffset;
+ fields->seconds = fields->localSeconds - fields->tzOffset;
+ return TCL_OK;
+ }
+
+ /*
* Unpack the tz data.
*/
@@ -1243,10 +1202,22 @@ ConvertLocalToUTC(
*/
if (rowc == 0) {
- return ConvertLocalToUTCUsingC(interp, fields, changeover);
+ if (ConvertLocalToUTCUsingC(interp, fields, changeover) != TCL_OK) {
+ return TCL_ERROR;
+ };
} else {
- return ConvertLocalToUTCUsingTable(interp, fields, rowc, rowv);
+ if (ConvertLocalToUTCUsingTable(interp, fields, rowc, rowv) != TCL_OK) {
+ return TCL_ERROR;
+ };
}
+
+ /* Cache the last conversion */
+ Tcl_SetObjRef(dataPtr->Local2UTC.tzData, tzdata);
+ dataPtr->Local2UTC.localSeconds = fields->localSeconds;
+ dataPtr->Local2UTC.changeover = changeover;
+ dataPtr->Local2UTC.tzOffset = fields->tzOffset;
+
+ return TCL_OK;
}
/*
@@ -1321,6 +1292,40 @@ ConvertLocalToUTCUsingTable(
}
fields->tzOffset = have[i];
fields->seconds = fields->localSeconds - fields->tzOffset;
+
+#if 0
+ /*
+ * Convert back from UTC, if local times are different - wrong local time
+ * (local time seems to be in between DST-hole).
+ */
+ if (fields->tzOffset) {
+
+ int corrOffset;
+ Tcl_WideInt backCompVal;
+ /* check DST-hole interval contains UTC time */
+ Tcl_GetWideIntFromObj(NULL, cellv[0], &backCompVal);
+ if ( fields->seconds >= backCompVal - fields->tzOffset
+ && fields->seconds <= backCompVal + fields->tzOffset
+ ) {
+ row = LookupLastTransition(interp, fields->seconds, rowc, rowv);
+ if (row == NULL ||
+ TclListObjGetElements(interp, row, &cellc, &cellv) != TCL_OK ||
+ TclGetIntFromObj(interp, cellv[1], &corrOffset) != TCL_OK) {
+ return TCL_ERROR;
+ }
+ if (fields->localSeconds != fields->seconds + corrOffset) {
+ Tcl_Panic("wrong local time %ld by LocalToUTC conversion,"
+ " local time seems to be in between DST-hole",
+ fields->localSeconds);
+ /* correcting offset * /
+ fields->tzOffset -= corrOffset;
+ fields->seconds += fields->tzOffset;
+ */
+ }
+ }
+ }
+#endif
+
return TCL_OK;
}
@@ -1424,15 +1429,34 @@ ConvertLocalToUTCUsingC(
static 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 */
int changeover) /* Julian Day of the Gregorian transition */
{
+ ClockClientData *dataPtr = clientData;
int rowc; /* Number of rows in tzdata */
Tcl_Obj **rowv; /* Pointers to the rows */
/*
+ * Check cacheable conversion could be used
+ * (last-minute UTC2Local cache with the same TZ)
+ */
+ if ( tzdata == dataPtr->UTC2Local.tzData
+ && ( fields->seconds == dataPtr->UTC2Local.seconds
+ || fields->seconds / 60 == dataPtr->UTC2Local.seconds / 60
+ )
+ && changeover == dataPtr->UTC2Local.changeover
+ ) {
+ /* the same time zone and offset (UTC time inside the last minute) */
+ Tcl_SetObjRef(fields->tzName, dataPtr->UTC2Local.tzName);
+ fields->tzOffset = dataPtr->UTC2Local.tzOffset;
+ fields->localSeconds = fields->seconds + fields->tzOffset;
+ return TCL_OK;
+ }
+
+ /*
* Unpack the tz data.
*/
@@ -1446,10 +1470,22 @@ ConvertUTCToLocal(
*/
if (rowc == 0) {
- return ConvertUTCToLocalUsingC(interp, fields, changeover);
+ if (ConvertUTCToLocalUsingC(interp, fields, changeover) != TCL_OK) {
+ return TCL_ERROR;
+ }
} else {
- return ConvertUTCToLocalUsingTable(interp, fields, rowc, rowv);
+ if (ConvertUTCToLocalUsingTable(interp, fields, rowc, rowv) != TCL_OK) {
+ return TCL_ERROR;
+ }
}
+
+ /* Cache the last conversion */
+ Tcl_SetObjRef(dataPtr->UTC2Local.tzData, tzdata);
+ dataPtr->UTC2Local.seconds = fields->seconds;
+ dataPtr->UTC2Local.changeover = changeover;
+ dataPtr->UTC2Local.tzOffset = fields->tzOffset;
+ Tcl_SetObjRef(dataPtr->UTC2Local.tzName, fields->tzName);
+ return TCL_OK;
}
/*
@@ -2373,6 +2409,7 @@ _ClockParseFmtScnArgs(
resOpts->localeObj = NULL;
resOpts->timezoneObj = NULL;
resOpts->baseObj = NULL;
+ resOpts->flags = 0;
for (i = 2; i < objc; i+=2) {
if (Tcl_GetIndexFromObj(interp, objv[i], options[forScan],
"option", 0, &optionIndex) != TCL_OK) {
@@ -2539,6 +2576,8 @@ ClockScanObjCmd(
ret = TCL_ERROR;
+ info->flags = 0;
+
if (opts.baseObj != NULL) {
if (Tcl_GetWideIntFromObj(interp, opts.baseObj, &baseVal) != TCL_OK) {
return TCL_ERROR;
@@ -2580,20 +2619,20 @@ ClockScanObjCmd(
}
Tcl_SetObjRef(yydate.tzName, opts.timezoneObj);
- /* check cached */
- if ( dataPtr->lastBaseTimeZone == opts.timezoneObj
- && dataPtr->lastBaseDate.seconds == baseVal) {
- memcpy(&yydate, &dataPtr->lastBaseDate, ClockCacheableDateFieldsSize);
+ /* check base fields already cached (by TZ, last-second cache) */
+ if ( dataPtr->lastBase.TimeZone == opts.timezoneObj
+ && dataPtr->lastBase.Date.seconds == baseVal) {
+ memcpy(&yydate, &dataPtr->lastBase.Date, ClockCacheableDateFieldsSize);
} else {
/* extact fields from base */
yydate.seconds = baseVal;
- if (ClockGetDateFields(interp, &yydate, yydate.tzData, GREGORIAN_CHANGE_DATE)
- != TCL_OK) {
+ if (ClockGetDateFields(clientData, interp, &yydate, yydate.tzData,
+ GREGORIAN_CHANGE_DATE) != TCL_OK) {
goto done;
}
/* cache last base */
- memcpy(&dataPtr->lastBaseDate, &yydate, ClockCacheableDateFieldsSize);
- dataPtr->lastBaseTimeZone = opts.timezoneObj;
+ memcpy(&dataPtr->lastBase.Date, &yydate, ClockCacheableDateFieldsSize);
+ Tcl_SetObjRef(dataPtr->lastBase.TimeZone, opts.timezoneObj);
}
/* seconds are in localSeconds (relative base date), so reset time here */
@@ -2612,7 +2651,7 @@ ClockScanObjCmd(
}
ret = ClockFreeScan(clientData, interp, info, objv[1], &opts);
}
-#if 0
+#if 1
else
if (1) {
/* TODO: Tcled Scan proc - */
@@ -2636,24 +2675,40 @@ ClockScanObjCmd(
goto done;
}
-
/* If needed assemble julianDay using new year, month, etc. */
- if (yydate.julianDay == CL_INVALIDATE) {
+ if (info->flags & CLF_INVALIDATE_JULIANDAY) {
GetJulianDayFromEraYearMonthDay(&yydate, GREGORIAN_CHANGE_DATE);
}
-
+
+ /* some overflow checks, if not extended */
+ if (!(opts.flags & CLF_EXTENDED)) {
+ if (yydate.julianDay > 5373484) {
+ Tcl_SetObjResult(interp, Tcl_NewStringObj(
+ "requested date too large to represent", -1));
+ Tcl_SetErrorCode(interp, "CLOCK", "dateTooLarge", NULL);
+ ret = TCL_ERROR;
+ goto done;
+ }
+ }
+
/* Local seconds to UTC (stored in yydate.seconds) */
- yydate.localSeconds =
- -210866803200L
- + ( 86400 * (Tcl_WideInt)yydate.julianDay )
- + ( yySeconds % 86400 );
+ if (info->flags & (CLF_INVALIDATE_SECONDS|CLF_INVALIDATE_JULIANDAY)) {
+ yydate.localSeconds =
+ -210866803200L
+ + ( SECONDS_PER_DAY * (Tcl_WideInt)yydate.julianDay )
+ + ( yySeconds % SECONDS_PER_DAY );
+ }
- if (ConvertLocalToUTC(interp, &yydate, yydate.tzData, GREGORIAN_CHANGE_DATE)
+ if (ConvertLocalToUTC(clientData, interp, &yydate, yydate.tzData, GREGORIAN_CHANGE_DATE)
!= TCL_OK) {
goto done;
}
+ /* Increment UTC seconds with relative time */
+
+ yydate.seconds += yyRelSeconds;
+
ret = TCL_OK;
done:
@@ -2681,7 +2736,7 @@ ClockFreeScan(
Tcl_Obj *strObj, /* String containing the time to scan */
ClockFmtScnCmdArgs *opts) /* Command options */
{
- // ClockClientData *dataPtr = clientData;
+ ClockClientData *dataPtr = clientData;
// Tcl_Obj **literals = dataPtr->literals;
int ret = TCL_ERROR;
@@ -2712,15 +2767,16 @@ ClockFreeScan(
if (yyHaveDate) {
if (yyYear < 100) {
- if (yyYear >= ClockGetYearOfCenturySwitch(clientData, interp)) {
+ if (yyYear >= dataPtr->yearOfCenturySwitch) {
yyYear -= 100;
}
- yyYear += ClockCurrentYearCentury(clientData, interp);
+ yyYear += dataPtr->currentYearCentury;
}
yydate.era = CE;
if (yyHaveTime == 0) {
yyHaveTime = -1;
}
+ info->flags |= CLF_INVALIDATE_JULIANDAY|CLF_INVALIDATE_SECONDS;
}
/*
@@ -2749,22 +2805,22 @@ ClockFreeScan(
Tcl_SetObjRef(yydate.tzName, opts->timezoneObj);
+ info->flags |= CLF_INVALIDATE_SECONDS;
}
- /* on demand (lazy) assemble julianDay using new year, month, etc. */
- yydate.julianDay = CL_INVALIDATE;
-
/*
* Assemble date, time, zone into seconds-from-epoch
*/
if (yyHaveTime == -1) {
yySeconds = 0;
+ info->flags |= CLF_INVALIDATE_SECONDS;
}
else
if (yyHaveTime) {
yySeconds = ToSeconds(yyHour, yyMinutes,
yySeconds, yyMeridian);
+ info->flags |= CLF_INVALIDATE_SECONDS;
}
else
if ( (yyHaveDay && !yyHaveDate)
@@ -2774,9 +2830,10 @@ ClockFreeScan(
|| yyRelDay != 0 ) )
) {
yySeconds = 0;
+ info->flags |= CLF_INVALIDATE_SECONDS;
}
else {
- yySeconds = yydate.localSeconds % 86400;
+ yySeconds = yydate.localSeconds % SECONDS_PER_DAY;
}
/*
@@ -2787,16 +2844,24 @@ repeat_rel:
if (yyHaveRel) {
+ /*
+ * 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 (yyMonth == CL_INVALIDATE) {
+ if (info->flags & CLF_INVALIDATE_DATE) {
GetGregorianEraYearDay(&yydate, GREGORIAN_CHANGE_DATE);
GetMonthDay(&yydate);
GetYearWeekDay(&yydate, GREGORIAN_CHANGE_DATE);
+ info->flags &= ~CLF_INVALIDATE_DATE;
}
/* add the requisite number of months */
@@ -2812,7 +2877,7 @@ repeat_rel:
}
/* on demand (lazy) assemble julianDay using new year, month, etc. */
- yydate.julianDay = CL_INVALIDATE;
+ info->flags |= CLF_INVALIDATE_JULIANDAY|CLF_INVALIDATE_SECONDS;
yyRelMonth = 0;
}
@@ -2821,21 +2886,33 @@ repeat_rel:
if (yyRelDay) {
/* assemble julianDay using new year, month, etc. */
- if (yydate.julianDay == CL_INVALIDATE) {
+ if (info->flags & CLF_INVALIDATE_JULIANDAY) {
GetJulianDayFromEraYearMonthDay(&yydate, GREGORIAN_CHANGE_DATE);
+ info->flags &= ~CLF_INVALIDATE_JULIANDAY;
}
- yydate.julianDay += yyRelDay;
+ yydate.julianDay += yyRelDay;
/* julianDay was changed, on demand (lazy) extract year, month, etc. again */
- yyMonth = CL_INVALIDATE;
+ info->flags |= CLF_INVALIDATE_DATE|CLF_INVALIDATE_SECONDS;
yyRelDay = 0;
}
- /* relative time (seconds) */
- yySeconds += yyRelSeconds;
- yyRelSeconds = 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) {
+ time_t newSecs = yySeconds + yyRelSeconds;
+
+ /* if seconds increment outside of current date, increment day */
+ if (newSecs / SECONDS_PER_DAY != yySeconds / SECONDS_PER_DAY) {
+
+ yyRelDay += newSecs / SECONDS_PER_DAY;
+ yySeconds = 0;
+ yyRelSeconds = newSecs % SECONDS_PER_DAY;
+
+ goto repeat_rel;
+ }
+ }
}
/*
@@ -2846,10 +2923,11 @@ repeat_rel:
int monthDiff;
/* if needed extract year, month, etc. again */
- if (yyMonth == CL_INVALIDATE) {
+ if (info->flags & CLF_INVALIDATE_DATE) {
GetGregorianEraYearDay(&yydate, GREGORIAN_CHANGE_DATE);
GetMonthDay(&yydate);
GetYearWeekDay(&yydate, GREGORIAN_CHANGE_DATE);
+ info->flags &= ~CLF_INVALIDATE_DATE;
}
if (yyMonthOrdinalIncr > 0) {
@@ -2872,6 +2950,8 @@ repeat_rel:
yyRelMonth += monthDiff;
yyHaveOrdinalMonth = 0;
+ info->flags |= CLF_INVALIDATE_JULIANDAY|CLF_INVALIDATE_SECONDS;
+
goto repeat_rel;
}
@@ -2882,8 +2962,9 @@ repeat_rel:
if (yyHaveDay && !yyHaveDate) {
/* if needed assemble julianDay now */
- if (yydate.julianDay == CL_INVALIDATE) {
+ if (info->flags & CLF_INVALIDATE_JULIANDAY) {
GetJulianDayFromEraYearMonthDay(&yydate, GREGORIAN_CHANGE_DATE);
+ info->flags &= ~CLF_INVALIDATE_JULIANDAY;
}
yydate.era = CE;
@@ -2892,6 +2973,7 @@ repeat_rel:
if (yyDayOrdinal > 0) {
yydate.julianDay -= 7;
}
+ info->flags |= CLF_INVALIDATE_DATE|CLF_INVALIDATE_SECONDS;
}
/* Free scanning completed - date ready */
diff --git a/generic/tclClockFmt.c b/generic/tclClockFmt.c
index b074681..48b9b69 100644
--- a/generic/tclClockFmt.c
+++ b/generic/tclClockFmt.c
@@ -35,6 +35,66 @@ static void ClockFmtScnStorageDelete(ClockFmtScnStorage *fss);
* Clock scan and format facilities.
*/
+inline int
+_str2int(
+ time_t *out,
+ register
+ const char *p,
+ const char *e,
+ int sign)
+{
+ register time_t 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;
+}
+
+inline int
+_str2wideInt(
+ Tcl_WideInt *out,
+ register
+ const char *p,
+ const char *e,
+ int sign)
+{
+ register 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;
+}
+
/*
*----------------------------------------------------------------------
*/
@@ -403,22 +463,35 @@ Tcl_GetClockFrmScnFromObj(
memset(tok, 0, sizeof(*(tok)));
const char *ScnSTokenMapChars =
- "dmyYHMS";
+ "dmyYHMSJs";
static ClockScanTokenMap ScnSTokenMap[] = {
- {CTOKT_DIGIT, CLF_DATE, 1, 2, TclOffset(DateInfo, date.dayOfMonth),
+ /* %d */
+ {CTOKT_DIGIT, CLF_DATE, 1, 2, TclOffset(DateInfo, date.dayOfMonth),
NULL},
+ /* %m */
{CTOKT_DIGIT, CLF_DATE, 1, 2, TclOffset(DateInfo, date.month),
NULL},
+ /* %y */
{CTOKT_DIGIT, CLF_DATE, 1, 2, TclOffset(DateInfo, date.year),
NULL},
+ /* %Y */
{CTOKT_DIGIT, CLF_DATE, 1, 4, TclOffset(DateInfo, date.year),
NULL},
+ /* %H */
{CTOKT_DIGIT, CLF_TIME, 1, 2, TclOffset(DateInfo, date.hour),
NULL},
+ /* %M */
{CTOKT_DIGIT, CLF_TIME, 1, 2, TclOffset(DateInfo, date.minutes),
NULL},
+ /* %S */
{CTOKT_DIGIT, CLF_TIME, 1, 2, TclOffset(DateInfo, date.secondOfDay),
NULL},
+ /* %J */
+ {CTOKT_DIGIT, CLF_DATE | CLF_JULIANDAY, 1, 0xffff, TclOffset(DateInfo, date.julianDay),
+ NULL},
+ /* %s */
+ {CTOKT_DIGIT, CLF_LOCALSEC | CLF_SIGNED, 1, 0xffff, TclOffset(DateInfo, date.localSeconds),
+ NULL},
};
const char *ScnSpecTokenMapChars =
" ";
@@ -576,6 +649,7 @@ ClockScan(
Tcl_Obj *strObj, /* String containing the time to scan */
ClockFmtScnCmdArgs *opts) /* Command options */
{
+ ClockClientData *dataPtr = clientData;
ClockScanToken *tok;
ClockScanTokenMap *map;
register const char *p, *x, *end;
@@ -590,46 +664,100 @@ ClockScan(
yyMeridian = MER24;
- /* bypass spaces at begin of string */
-
p = TclGetString(strObj);
end = p + strObj->length;
- while (p < end && isspace(UCHAR(*p))) {
- p++;
+ /* in strict mode - bypass spaces at begin / end only (not between tokens) */
+ if (opts->flags & CLF_STRICT) {
+ while (p < end && isspace(UCHAR(*p))) {
+ p++;
+ }
}
info->dateStart = yyInput = p;
/* parse string */
- for (; tok->map != NULL; yyInput = p, tok++) {
+ 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) {
+ while (p < end && isspace(UCHAR(*p))) {
+ p++;
+ }
+ yyInput = p;
+ }
switch (map->type)
{
case CTOKT_DIGIT:
if (1) {
- unsigned int val = 0;
int size = map->maxSize;
- /* greedy find digits (look forward), corresponding pre-calculated lookAhead */
- size += tok->lookAhead;
- x = yyInput + size;
- while (isdigit(UCHAR(*p)) && p < x) { p++; };
- /* consider reserved (lookAhead) for next tokens */
- p -= tok->lookAhead;
+ int sign = 1;
+ if (map->flags & CLF_SIGNED) {
+ if (*p == '+') { yyInput = ++p; }
+ else
+ if (*p == '-') { yyInput = ++p; sign = -1; };
+ }
+ /* greedy find digits (look for forward digits consider spaces),
+ * corresponding pre-calculated lookAhead */
+ if (size != map->minSize && tok->lookAhead) {
+ int spcnt = 0;
+ const char *pe;
+ size += tok->lookAhead;
+ x = p + size; if (x > end) { x = end; };
+ pe = x;
+ while (p < x) {
+ if (isspace(UCHAR(*p))) {
+ if (pe > p) { pe = p; };
+ if (x < end) x++;
+ p++;
+ spcnt++;
+ continue;
+ }
+ if (isdigit(UCHAR(*p))) {
+ p++;
+ continue;
+ }
+ break;
+ }
+ /* consider reserved (lookAhead) for next tokens */
+ p -= tok->lookAhead + spcnt;
+ if (p > pe) {
+ p = pe;
+ }
+ } else {
+ x = p + size; if (x > end) { x = end; };
+ while (isdigit(UCHAR(*p)) && p < x) { p++; };
+ }
size = p - yyInput;
if (size < map->minSize) {
/* missing input -> error */
- goto done;
+ goto error;
}
- /* string 2 number */
+ /* string 2 number, put number into info structure by offset */
p = yyInput; x = p + size;
- while (p < x) {
- val = val * 10 + (*p++ - '0');
+ if (!(map->flags & CLF_LOCALSEC)) {
+ if (_str2int((time_t *)(((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;
}
- /* put number into info by offset */
- *(time_t *)(((char *)info) + map->offs) = val;
flags |= map->flags;
}
break;
case CTOKT_SPACE:
+ /* at least one space in strict mode */
+ if (opts->flags & CLF_STRICT) {
+ if (!isspace(UCHAR(*p))) {
+ /* unmatched -> error */
+ goto error;
+ }
+ p++;
+ }
while (p < end && isspace(UCHAR(*p))) {
p++;
}
@@ -639,7 +767,7 @@ ClockScan(
if (x == tok->tokWord.end) { /* single char word */
if (*p != *x) {
/* no match -> error */
- goto done;
+ goto error;
}
p++;
continue;
@@ -648,7 +776,7 @@ ClockScan(
while (p < end && x < tok->tokWord.end && *p++ == *x++) {};
if (x < tok->tokWord.end) {
/* no match -> error */
- goto done;
+ goto error;
}
break;
}
@@ -661,43 +789,57 @@ ClockScan(
/* check end was reached */
if (p < end) {
/* something after last token - wrong format */
- goto done;
+ goto error;
}
/* invalidate result */
if (flags & CLF_DATE) {
- yydate.julianDay = CL_INVALIDATE;
+ if (!(flags & CLF_JULIANDAY)) {
+ info->flags |= CLF_INVALIDATE_SECONDS|CLF_INVALIDATE_JULIANDAY;
- if (yyYear < 100) {
- if (yyYear >= ClockGetYearOfCenturySwitch(clientData, interp)) {
- yyYear -= 100;
+ if (yyYear < 100) {
+ if (yyYear >= dataPtr->yearOfCenturySwitch) {
+ yyYear -= 100;
+ }
+ yyYear += dataPtr->currentYearCentury;
}
- yyYear += ClockCurrentYearCentury(clientData, interp);
+ yydate.era = CE;
}
- yydate.era = CE;
- if (!(flags & CLF_TIME)) {
+ /* if date but no time - reset time */
+ if (!(flags & (CLF_TIME|CLF_LOCALSEC))) {
+ info->flags |= CLF_INVALIDATE_SECONDS;
yydate.localSeconds = 0;
}
}
if (flags & CLF_TIME) {
+ info->flags |= CLF_INVALIDATE_SECONDS;
yySeconds = ToSeconds(yyHour, yyMinutes,
yySeconds, yyMeridian);
- } else {
- yySeconds = yydate.localSeconds % 86400;
+ } else
+ if (!(flags & CLF_LOCALSEC)) {
+ info->flags |= CLF_INVALIDATE_SECONDS;
+ yySeconds = yydate.localSeconds % SECONDS_PER_DAY;
}
ret = TCL_OK;
+ goto done;
-done:
+overflow:
- if (ret != TCL_OK) {
- Tcl_SetResult(interp,
- "input string does not match supplied format", TCL_STATIC);
- Tcl_SetErrorCode(interp, "CLOCK", "badInputString", NULL);
- return ret;
- }
+ Tcl_SetObjResult(interp, Tcl_NewStringObj(
+ "integer value too large to represent", -1));
+ Tcl_SetErrorCode(interp, "CLOCK", "integervalueTooLarge", NULL);
+ goto done;
+
+error:
+
+ Tcl_SetResult(interp,
+ "input string does not match supplied format", TCL_STATIC);
+ Tcl_SetErrorCode(interp, "CLOCK", "badInputString", NULL);
+
+done:
return ret;
}
diff --git a/generic/tclDate.h b/generic/tclDate.h
index 4b0b47e..47f2a21 100644
--- a/generic/tclDate.h
+++ b/generic/tclDate.h
@@ -14,6 +14,28 @@
#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 */
+
+
+/* On demand (lazy) assemble flags */
+#define CLF_INVALIDATE_DATE (1 << 6) /* assemble year, month, etc. using julianDay */
+#define CLF_INVALIDATE_JULIANDAY (1 << 7) /* assemble julianDay using year, month, etc. */
+#define CLF_INVALIDATE_SECONDS (1 << 8) /* assemble localSeconds (and seconds at end) */
+
+/*
* Structure containing the fields used in [clock format] and [clock scan]
*/
@@ -52,6 +74,7 @@ typedef struct TclDateFields {
typedef struct DateInfo {
const char *dateStart;
const char *dateInput;
+ int flags;
TclDateFields date;
@@ -116,19 +139,71 @@ typedef struct DateInfo {
#define yyDigitCount (info->dateDigitCount)
-enum {CL_INVALIDATE = (signed int)0x80000000};
/*
* Structure containing the command arguments supplied to [clock format] and [clock scan]
*/
+#define CLF_EXTENDED (1 << 4)
+#define CLF_STRICT (1 << 8)
+
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) */
+ int flags; /* Flags control scanning */
} ClockFmtScnCmdArgs;
/*
+ * Structure containing the client data for [clock]
+ */
+
+typedef struct ClockClientData {
+ int refCount; /* Number of live references. */
+ Tcl_Obj **literals; /* Pool of object literals. */
+ /* Cache for current clock parameters, imparted via "configure" */
+ unsigned long LastTZEpoch;
+ int currentYearCentury;
+ int yearOfCenturySwitch;
+ Tcl_Obj *SystemTimeZone;
+ Tcl_Obj *SystemSetupTZData;
+ Tcl_Obj *GMTSetupTimeZone;
+ Tcl_Obj *GMTSetupTZData;
+ Tcl_Obj *AnySetupTimeZone;
+ Tcl_Obj *AnySetupTZData;
+ Tcl_Obj *LastUnnormSetupTimeZone;
+ Tcl_Obj *LastSetupTimeZone;
+ Tcl_Obj *LastSetupTZData;
+ /* Cache for last base (last-second fast convert if base/tz not changed) */
+ struct {
+ Tcl_Obj *TimeZone;
+ TclDateFields Date;
+ } lastBase;
+ /* Las-minute cache for fast UTC2Local conversion */
+ struct {
+ /* keys */
+ Tcl_Obj *tzData;
+ int changeover;
+ Tcl_WideInt seconds;
+ /* values */
+ time_t tzOffset;
+ Tcl_Obj *tzName;
+ } UTC2Local;
+ /* Las-minute cache for fast Local2UTC conversion */
+ struct {
+ /* keys */
+ Tcl_Obj *tzData;
+ int changeover;
+ Tcl_WideInt localSeconds;
+ /* values */
+ time_t tzOffset;
+ } Local2UTC;
+} ClockClientData;
+
+#define ClockDefaultYearCentury 2000
+#define ClockDefaultCenturySwitch 38
+
+/*
* Meridian: am, pm, or 24-hour style.
*/
@@ -152,8 +227,11 @@ typedef int ClockScanTokenProc(
ClockScanToken *tok);
-#define CLF_DATE (1 << 2)
-#define CLF_TIME (1 << 3)
+#define CLF_DATE (1 << 2)
+#define CLF_JULIANDAY (1 << 3)
+#define CLF_TIME (1 << 4)
+#define CLF_LOCALSEC (1 << 5)
+#define CLF_SIGNED (1 << 8)
typedef enum _CLCKTOK_TYPE {
CTOKT_DIGIT = 1, CTOKT_SPACE, CTOKT_WORD
diff --git a/library/clock.tcl b/library/clock.tcl
index ace4176..e0de904 100755
--- a/library/clock.tcl
+++ b/library/clock.tcl
@@ -287,9 +287,10 @@ proc ::tcl::clock::Initialize {} {
variable FEB_28 58
- # Current year century and year of century switch
- variable CurrentYearCentury 2000
- variable YearOfCenturySwitch 38
+ # Default configuration
+
+ # 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
@@ -2534,13 +2535,11 @@ proc ::tcl::clock::ScanWide { str } {
proc ::tcl::clock::InterpretTwoDigitYear { date baseTime
{ twoDigitField yearOfCentury }
{ fourDigitField year } } {
- variable CurrentYearCentury
- variable YearOfCenturySwitch
set yr [dict get $date $twoDigitField]
- if { $yr >= $YearOfCenturySwitch } {
+ if { $yr >= [configure -century-switch] } {
incr yr -100
}
- incr yr $CurrentYearCentury
+ incr yr [configure -year-century]
dict set date $fourDigitField $yr
return $date
}
diff --git a/tests-perf/clock.perf.tcl b/tests-perf/clock.perf.tcl
index 93a78e4..9267684 100644
--- a/tests-perf/clock.perf.tcl
+++ b/tests-perf/clock.perf.tcl
@@ -15,10 +15,19 @@
# of this file.
#
+
+## set testing defaults:
set ::env(TCL_TZ) :CET
+## warm-up (load clock.tcl, system zones, etc.):
+clock scan "" -gmt 1
+clock scan ""
+clock scan "" -timezone :CET
+
+## ------------------------------------------
+
proc {**STOP**} {args} {
- return -code error -level 2 "**STOP** in [info level [expr {[info level]-1}]] [join $args { }]"
+ return -code error -level 4 "**STOP** in [info level [expr {[info level]-2}]] [join $args { }]"
}
proc _test_get_commands {lst} {
@@ -43,7 +52,7 @@ proc _test_out_total {} {
puts ""
}
-proc _test_run {reptime lst {outcmd {puts {$_(r)}}}} {
+proc _test_run {reptime lst {outcmd {puts $_(r)}}} {
upvar _ _
array set _ [list ittm {} itcnt {} itrate {} reptime $reptime]
@@ -74,6 +83,22 @@ proc test-scan {{reptime 1000}} {
{clock scan "1111" -format "%d%m%y" -base 0 -gmt 1}
{clock scan "11111" -format "%d%m%y" -base 0 -gmt 1}
{clock scan "111111" -format "%d%m%y" -base 0 -gmt 1}
+ # Scan : greedy match (space separated)
+ {clock scan "1 1 1" -format "%d%m%y" -base 0 -gmt 1}
+ {clock scan "111 1" -format "%d%m%y" -base 0 -gmt 1}
+ {clock scan "1 111" -format "%d%m%y" -base 0 -gmt 1}
+ {clock scan "1 11 1" -format "%d%m%y" -base 0 -gmt 1}
+ {clock scan "1 11 11" -format "%d%m%y" -base 0 -gmt 1}
+ {clock scan "11 11 11" -format "%d%m%y" -base 0 -gmt 1}
+
+ # Scan : time (in gmt)
+ {clock scan "10:35:55" -format "%H:%M:%S" -base 1000000000 -gmt 1}
+ # Scan : time (system time zone, with base)
+ {clock scan "10:35:55" -format "%H:%M:%S" -base 1000000000}
+ # Scan : time (gmt, without base)
+ {clock scan "10:35:55" -format "%H:%M:%S" -gmt 1}
+ # Scan : time (system time zone, without base)
+ {clock scan "10:35:55" -format "%H:%M:%S"}
# Scan : date-time (in gmt)
{clock scan "25.11.2015 10:35:55" -format "%d.%m.%Y %H:%M:%S" -base 0 -gmt 1}
@@ -85,6 +110,17 @@ proc test-scan {{reptime 1000}} {
# Scan : dynamic format (cacheable)
{clock scan "25.11.2015 10:35:55" -format [string trim "%d.%m.%Y %H:%M:%S "] -base 0 -gmt 1}
+ # Scan : julian day in gmt
+ {clock scan 2451545 -format %J -gmt 1}
+ # Scan : julian day in system TZ
+ {clock scan 2451545 -format %J}
+ # Scan : julian day in other TZ
+ {clock scan 2451545 -format %J -timezone +0200}
+ # Scan : julian day with time:
+ {clock scan "2451545 10:20:30" -format "%J %H:%M:%S"}
+ # Scan : julian day with time (greedy match):
+ {clock scan "2451545 102030" -format "%J%H%M%S"}
+
# Scan : zone only
{clock scan "CET" -format "%z"}
{clock scan "EST" -format "%z"}
@@ -144,6 +180,10 @@ proc test-other {{reptime 1000}} {
_test_run $reptime {
# Bad zone
{catch {clock scan "1 day" -timezone BAD_ZONE -locale en}}
+
+ # Scan : julian day (overflow)
+ {catch {clock scan 5373485 -format %J}}
+
**STOP**
# 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}
@@ -154,11 +194,11 @@ proc test-other {{reptime 1000}} {
proc test {{reptime 1000}} {
puts ""
- test-scan $reptime
- #test-freescan $reptime
+ #test-scan $reptime
+ test-freescan $reptime
test-other $reptime
puts \n**OK**
}
-test 250; # 250 ms
+test 100; # ms
diff --git a/tests/clock.test b/tests/clock.test
index 477f142..9e3dbd7 100644
--- a/tests/clock.test
+++ b/tests/clock.test
@@ -35917,7 +35917,94 @@ test clock-34.52 {more than one ordinal month} {*}{
-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.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} {