Adjust date/time functions so that they do a better job of keeping track

of whether the current time is UTC or localtime, and no-op the 'utc' and
'localtime' modifiers accordingly. See
[forum:/info/e7a939e074|forum post e7a939e074].
Also add the datedebug() function, available
only under -DSQLITE_DEBUG, for improved visibility of the DateTime object
during debugging and testing.

FossilOrigin-Name: dc569683748354a6db83438904422e802d3ea780775c48da85b474fff03ca8a1
This commit is contained in:
drh 2024-03-04 13:58:09 +00:00
parent 8d25d07d50
commit 3f273db39b
5 changed files with 103 additions and 29 deletions

View File

@ -1,5 +1,5 @@
C Fix\sassert()\sstatements\sin\sdate/time\scomputations:\sThe\smonth\sand\sday\snumbers\ncan\sbe\szero\sif\san\serror\shas\sbeen\sseen.
D 2024-03-04T11:12:15.305
C Adjust\sdate/time\sfunctions\sso\sthat\sthey\sdo\sa\sbetter\sjob\sof\skeeping\strack\nof\swhether\sthe\scurrent\stime\sis\sUTC\sor\slocaltime,\sand\sno-op\sthe\s'utc'\sand\n'localtime'\smodifiers\saccordingly.\sSee\n[forum:/info/e7a939e074|forum\spost\se7a939e074].\nAlso\sadd\sthe\sdatedebug()\sfunction,\savailable\nonly\sunder\s-DSQLITE_DEBUG,\sfor\simproved\svisibility\sof\sthe\sDateTime\sobject\nduring\sdebugging\sand\stesting.
D 2024-03-04T13:58:09.237
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
@ -696,7 +696,7 @@ F src/build.c 04f1bcee189f045ab086d84fee95db42cb49df82ff8e84af8136309ff3c8a75f
F src/callback.c db3a45e376deff6a16c0058163fe0ae2b73a2945f3f408ca32cf74960b28d490
F src/complete.c a3634ab1e687055cd002e11b8f43eb75c17da23e
F src/ctime.c 23331529e654be40ca97d171cbbffe9b3d4c71cc53b78fe5501230675952da8b
F src/date.c 29cecfe69287242a12f753ffc883e4231afde3af501fb1b4a7b721b159fc4bb2
F src/date.c a9e4382961fb26156a308645f7363519ab7eb20e412e78b7c1fe16bbfbb1689a
F src/dbpage.c 80e46e1df623ec40486da7a5086cb723b0275a6e2a7b01d9f9b5da0f04ba2782
F src/dbstat.c 3b677254d512fcafd4d0b341bf267b38b235ccfddbef24f9154e19360fa22e43
F src/delete.c cb766727c78e715f9fb7ec8a7d03658ed2a3016343ca687acfcec9083cdca500
@ -1042,7 +1042,7 @@ F test/ctime.test 340f362f41f92972bbd71f44e10569a5cc694062b692231bd08aa6fe6c1c47
F test/cursorhint.test 05cf0febe5c5f8a31f199401fd1c9322249e753950d55f26f9d5aca61408a270
F test/cursorhint2.test 6f3aa9cb19e7418967a10ec6905209bcbb5968054da855fc36c8beee9ae9c42f
F test/dataversion1.test 6e5e86ac681f0782e766ebcb56c019ae001522d114e0e111e5ebf68ccf2a7bb8
F test/date.test 99bfd3a77a3f9ae54eebd374a4301af960f2b2e9a581cf63e26445bae830a435
F test/date.test c8ff835023f2107b57ce7a45c92265d51c98a23fc93231e998f12d850831aad6
F test/date2.test 7e12ec14aaf4d5e6294b4ba140445b0eca06ea50062a9c3a69c4ee13d0b6f8b1
F test/date3.test a1b77abf05c6772fe5ca2337cac1398892f2a41e62bce7e6be0f4a08a0e64ae5
F test/date4.test 75dc8401e8c0639a228cd26a6eaa4ff5ea8ccda912b9853d1c9462c476670e17
@ -1749,7 +1749,7 @@ F test/tkt-b351d95f9.test d14a503c414c5c58fdde3e80f9a3cfef986498c0
F test/tkt-b72787b1.test a95e8cdad0b98af1853ac7f0afd4ab27b77bf5f3
F test/tkt-b75a9ca6b0.test dc6a853c242f7d0326564ae32e9e5eb462b5e8d2bc5b01ea3b18fd24f8e5894b
F test/tkt-ba7cbfaedc.test b4c0deccc12aeb55cfdb57935b16b5d67c5a9877
F test/tkt-bd484a090c.test 60460bf946f79a79712b71f202eda501ca99b898
F test/tkt-bd484a090c.test e6af3e3a4242cd8f1c91c736364f09075d8e33e3b86f6492a1ee36278ea71b61
F test/tkt-bdc6bbbb38.test fc38bb09bdd440e3513a1f5f98fc60a075182d7d
F test/tkt-c48d99d690.test ba61977d62ab612fc515b3c488a6fbd6464a2447
F test/tkt-c694113d5.test 82c461924ada5c14866c47e85535b0b0923ba16a2e907e370061a5ca77f65d77
@ -2176,8 +2176,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
P f6e887203365b30cea6e9c145366345e48256a347388577edf2bada65e0655b7
R 68522a41082e7767e1ccbf426313c9cf
P fc773f6c76ec114be8b6e25b13885acb5adcc9d052dca0d8d0ff94e2a0743d64
R 0f693fc7ce53368753332e9082e39c9c
U drh
Z cb9173c28803c9d207f1634b7853f7aa
Z b0035812b357d7d3f0f59f737bf36230
# Remove this line to create a well-formed Fossil manifest.

View File

@ -1 +1 @@
fc773f6c76ec114be8b6e25b13885acb5adcc9d052dca0d8d0ff94e2a0743d64
dc569683748354a6db83438904422e802d3ea780775c48da85b474fff03ca8a1

View File

@ -73,12 +73,12 @@ struct DateTime {
char validJD; /* True (1) if iJD is valid */
char validYMD; /* True (1) if Y,M,D are valid */
char validHMS; /* True (1) if h,m,s are valid */
char validTZ; /* True (1) if tz is valid */
char nFloor; /* Days to implement "floor" */
unsigned rawS : 1; /* Raw numeric value stored in s */
unsigned tzSet : 1; /* Timezone was set explicitly */
unsigned isError : 1; /* An overflow has occurred */
unsigned useSubsec : 1; /* Display subsecond precision */
unsigned isUtc : 1; /* Time is known to be UTC */
unsigned isLocal : 1; /* Time is known to be localtime */
};
@ -176,6 +176,8 @@ static int parseTimezone(const char *zDate, DateTime *p){
sgn = +1;
}else if( c=='Z' || c=='z' ){
zDate++;
p->isLocal = 0;
p->isUtc = 1;
goto zulu_time;
}else{
return c!=0;
@ -188,7 +190,6 @@ static int parseTimezone(const char *zDate, DateTime *p){
p->tz = sgn*(nMn + nHr*60);
zulu_time:
while( sqlite3Isspace(*zDate) ){ zDate++; }
p->tzSet = 1;
return *zDate!=0;
}
@ -232,7 +233,6 @@ static int parseHhMmSs(const char *zDate, DateTime *p){
p->m = m;
p->s = s + ms;
if( parseTimezone(zDate, p) ) return 1;
p->validTZ = (p->tz!=0)?1:0;
return 0;
}
@ -279,11 +279,13 @@ static void computeJD(DateTime *p){
p->validJD = 1;
if( p->validHMS ){
p->iJD += p->h*3600000 + p->m*60000 + (sqlite3_int64)(p->s*1000 + 0.5);
if( p->validTZ ){
if( p->tz ){
p->iJD -= p->tz*60000;
p->validYMD = 0;
p->validHMS = 0;
p->validTZ = 0;
p->tz = 0;
p->isUtc = 1;
p->isLocal = 0;
}
}
}
@ -350,11 +352,14 @@ static int parseYyyyMmDd(const char *zDate, DateTime *p){
p->M = M;
p->D = D;
computeFloor(p);
if( p->validTZ ){
if( p->tz ){
computeJD(p);
}
return 0;
}
};
static void clearYMD_HMS_TZ(DateTime *p); /* Forward declaration */
/*
** Set the time to the current time reported by the VFS.
@ -365,6 +370,9 @@ static int setDateTimeToCurrent(sqlite3_context *context, DateTime *p){
p->iJD = sqlite3StmtCurrentTime(context);
if( p->iJD>0 ){
p->validJD = 1;
p->isUtc = 1;
p->isLocal = 0;
clearYMD_HMS_TZ(p);
return 0;
}else{
return 1;
@ -503,7 +511,7 @@ static void computeYMD_HMS(DateTime *p){
static void clearYMD_HMS_TZ(DateTime *p){
p->validYMD = 0;
p->validHMS = 0;
p->validTZ = 0;
p->tz = 0;
}
#ifndef SQLITE_OMIT_LOCALTIME
@ -635,7 +643,7 @@ static int toLocaltime(
p->validHMS = 1;
p->validJD = 0;
p->rawS = 0;
p->validTZ = 0;
p->tz = 0;
p->isError = 0;
return SQLITE_OK;
}
@ -793,7 +801,9 @@ static int parseModifier(
** show local time.
*/
if( sqlite3_stricmp(z, "localtime")==0 && sqlite3NotPureFunc(pCtx) ){
rc = toLocaltime(p, pCtx);
rc = p->isLocal ? SQLITE_OK : toLocaltime(p, pCtx);
p->isUtc = 0;
p->isLocal = 1;
}
break;
}
@ -818,7 +828,7 @@ static int parseModifier(
}
#ifndef SQLITE_OMIT_LOCALTIME
else if( sqlite3_stricmp(z, "utc")==0 && sqlite3NotPureFunc(pCtx) ){
if( p->tzSet==0 ){
if( p->isUtc==0 ){
i64 iOrigJD; /* Original localtime */
i64 iGuess; /* Guess at the corresponding utc time */
int cnt = 0; /* Safety to prevent infinite loop */
@ -841,7 +851,8 @@ static int parseModifier(
memset(p, 0, sizeof(*p));
p->iJD = iGuess;
p->validJD = 1;
p->tzSet = 1;
p->isUtc = 1;
p->isLocal = 0;
}
rc = SQLITE_OK;
}
@ -861,7 +872,7 @@ static int parseModifier(
&& r>=0.0 && r<7.0 && (n=(int)r)==r ){
sqlite3_int64 Z;
computeYMD_HMS(p);
p->validTZ = 0;
p->tz = 0;
p->validJD = 0;
computeJD(p);
Z = ((p->iJD + 129600000)/86400000) % 7;
@ -901,7 +912,7 @@ static int parseModifier(
p->h = p->m = 0;
p->s = 0.0;
p->rawS = 0;
p->validTZ = 0;
p->tz = 0;
p->validJD = 0;
if( sqlite3_stricmp(z,"month")==0 ){
p->D = 1;
@ -1674,9 +1685,7 @@ static void timediffFunc(
d1.iJD = d2.iJD - d1.iJD;
d1.iJD += (u64)1486995408 * (u64)100000;
}
d1.validYMD = 0;
d1.validHMS = 0;
d1.validTZ = 0;
clearYMD_HMS_TZ(&d1);
computeYMD_HMS(&d1);
sqlite3StrAccumInit(&sRes, 0, 0, 0, 100);
sqlite3_str_appendf(&sRes, "%c%04d-%02d-%02d %02d:%02d:%06.3f",
@ -1745,6 +1754,36 @@ static void currentTimeFunc(
}
#endif
#if !defined(SQLITE_OMIT_DATETIME_FUNCS) && defined(SQLITE_DEBUG)
/*
** datedebug(...)
**
** This routine returns JSON that describes the internal DateTime object.
** Used for debugging and testing only. Subject to change.
*/
static void datedebugFunc(
sqlite3_context *context,
int argc,
sqlite3_value **argv
){
DateTime x;
if( isDate(context, argc, argv, &x)==0 ){
char *zJson;
zJson = sqlite3_mprintf(
"{iJD:%lld,Y:%d,M:%d,D:%d,h:%d,m:%d,tz:%d,"
"s:%.3f,validJD:%d,validYMS:%d,validHMS:%d,"
"nFloor:%d,rawS:%d,isError:%d,useSubsec:%d,"
"isUtc:%d,isLocal:%d}",
x.iJD, x.Y, x.M, x.D, x.h, x.m, x.tz,
x.s, x.validJD, x.validYMD, x.validHMS,
x.nFloor, x.rawS, x.isError, x.useSubsec,
x.isUtc, x.isLocal);
sqlite3_result_text(context, zJson, -1, sqlite3_free);
}
}
#endif /* !SQLITE_OMIT_DATETIME_FUNCS && SQLITE_DEBUG */
/*
** This function registered all of the above C functions as SQL
** functions. This should be the only routine in this file with
@ -1760,6 +1799,9 @@ void sqlite3RegisterDateTimeFunctions(void){
PURE_DATE(datetime, -1, 0, 0, datetimeFunc ),
PURE_DATE(strftime, -1, 0, 0, strftimeFunc ),
PURE_DATE(timediff, 2, 0, 0, timediffFunc ),
#ifdef SQLITE_DEBUG
PURE_DATE(datedebug, -1, 0, 0, datedebugFunc ),
#endif
DFUNCTION(current_time, 0, 0, 0, ctimeFunc ),
DFUNCTION(current_timestamp, 0, 0, 0, ctimestampFunc),
DFUNCTION(current_date, 0, 0, 0, cdateFunc ),

View File

@ -262,7 +262,7 @@ datetest 5.15 {datetime('1994-04-16 14:00:00 +05:00 Z')} NULL
# localtime->utc and utc->localtime conversions.
#
# Use SQLITE_TESTCTRL_LOCALTIME_FAULT=2 to set an alternative localtime_r()
# implementation that is not locale-dependent. This testing localtime_r()
# implementation that is not locale-dependent. The testing localtime_r()
# operates as follows:
#
# (1) Localtime is 30 minutes earlier than (west of) UTC on
@ -321,6 +321,38 @@ utc_to_local 6.22 {1800-10-29 12:30:00} {1800-10-29 12:00:00}
local_to_utc 6.23 {3000-10-30 12:00:00} {3000-10-30 11:30:00}
utc_to_local 6.24 {3000-10-30 11:30:00} {3000-10-30 12:00:00}
# If the time is specified to be ZULU, or if it has an explicit
# timezone extension, then the time will already be UTC and subsequent
# 'utc' modifiers are no-ops.
#
do_execsql_test date-6.25 {
SELECT datetime('2000-10-29 12:00Z','utc','utc');
} {{2000-10-29 12:00:00}}
do_execsql_test date-6.26 {
SELECT datetime('2000-10-29 12:00:00+05:00');
} {{2000-10-29 07:00:00}}
do_execsql_test date-6.27 {
SELECT datetime('2000-10-29 12:00:00+05:00', 'utc');
} {{2000-10-29 07:00:00}}
# Multiple back-and-forth UTC to LOCAL to UTC...
do_execsql_test date-6.28 {
SELECT datetime('2000-10-29 12:00:00Z', 'localtime');
} {{2000-10-29 12:30:00}}
do_execsql_test date-6.29 {
SELECT datetime('2000-10-29 12:00:00Z', 'utc', 'localtime');
} {{2000-10-29 12:30:00}}
do_execsql_test date-6.30 {
SELECT datetime('2000-10-29 12:00:00Z', 'utc', 'localtime', 'utc');
} {{2000-10-29 12:00:00}}
do_execsql_test date-6.31 {
SELECT datetime('2000-10-29 12:00:00Z', 'utc','localtime','utc','localtime');
} {{2000-10-29 12:30:00}}
do_execsql_test date-6.32 {
SELECT datetime('2000-10-29 12:00:00Z', 'localtime','localtime');
} {{2000-10-29 12:30:00}}
# Restore the use of the OS localtime_r() before going on...
sqlite3_test_control SQLITE_TESTCTRL_LOCALTIME_FAULT 0

View File

@ -30,7 +30,7 @@ do_test 2.1 {
catchsql { SELECT datetime('now', 'localtime') }
} {1 {local time unavailable}}
do_test 2.2 {
catchsql { SELECT datetime('now', 'utc') }
catchsql { SELECT datetime('2000-01-01', 'utc') }
} {1 {local time unavailable}}
sqlite3_test_control SQLITE_TESTCTRL_LOCALTIME_FAULT 0