1
0
mirror of https://github.com/sqlite/sqlite.git synced 2025-07-29 08:01:23 +03:00

Revised date/time functions - now broken out into a separate source file.

See the DateAndTimeFunctions wiki page for additional information. (CVS 1116)

FossilOrigin-Name: 68ef9b45bd3abdedf3721011ad0fb22e8735e721
This commit is contained in:
drh
2003-11-01 01:53:53 +00:00
parent b48484a6d7
commit 7014aff37d
8 changed files with 922 additions and 411 deletions

View File

@ -79,7 +79,7 @@ endif
# Object files for the SQLite library.
#
LIBOBJ = attach.lo auth.lo btree.lo build.lo copy.lo \
LIBOBJ = attach.lo auth.lo btree.lo build.lo copy.lo date.lo \
delete.lo expr.lo func.lo hash.lo insert.lo \
main.lo opcodes.lo os.lo pager.lo parse.lo pragma.lo \
printf.lo random.lo select.lo table.lo tokenize.lo \
@ -101,6 +101,7 @@ SRC = \
$(TOP)/src/btree_rb.c \
$(TOP)/src/build.c \
$(TOP)/src/copy.c \
$(TOP)/src/date.c \
$(TOP)/src/delete.c \
$(TOP)/src/expr.c \
$(TOP)/src/func.c \
@ -288,6 +289,9 @@ where.lo: $(TOP)/src/where.c $(HDR)
copy.lo: $(TOP)/src/copy.c $(HDR)
$(LIBTOOL) $(TCC) -c $(TOP)/src/copy.c
date.lo: $(TOP)/src/date.c $(HDR)
$(LIBTOOL) $(TCC) -c $(TOP)/src/date.c
delete.lo: $(TOP)/src/delete.c $(HDR)
$(LIBTOOL) $(TCC) -c $(TOP)/src/delete.c

View File

@ -54,7 +54,7 @@ TCCX = $(TCC) $(OPTS) $(THREADSAFE) $(USLEEP) -I. -I$(TOP)/src
# Object files for the SQLite library.
#
LIBOBJ = attach.o auth.o btree.o btree_rb.o build.o copy.o delete.o \
LIBOBJ = attach.o auth.o btree.o btree_rb.o build.o copy.o date.o delete.o \
expr.o func.o hash.o insert.o \
main.o opcodes.o os.o pager.o parse.o pragma.o printf.o random.o \
select.o table.o tokenize.o trigger.o update.o util.o \
@ -70,6 +70,7 @@ SRC = \
$(TOP)/src/btree_rb.c \
$(TOP)/src/build.c \
$(TOP)/src/copy.c \
$(TOP)/src/date.c \
$(TOP)/src/delete.c \
$(TOP)/src/expr.c \
$(TOP)/src/func.c \
@ -259,6 +260,9 @@ where.o: $(TOP)/src/where.c $(HDR)
copy.o: $(TOP)/src/copy.c $(HDR)
$(TCCX) -c $(TOP)/src/copy.c
date.o: $(TOP)/src/date.c $(HDR)
$(TCCX) -c $(TOP)/src/date.c
delete.o: $(TOP)/src/delete.c $(HDR)
$(TCCX) -c $(TOP)/src/delete.c

View File

@ -1,6 +1,6 @@
C Fork\sthe\stree\sfor\sproject\s"shrike"\s(CVS\s1118)
D 2003-10-30T07:00:00
F Makefile.in ab585a91e34bc33928a1b6181fa2f6ebd4fb17e1
C Revised\sdate/time\sfunctions\s-\snow\sbroken\sout\sinto\sa\sseparate\ssource\sfile.\nSee\sthe\sDateAndTimeFunctions\swiki\spage\sfor\sadditional\sinformation.\s(CVS\s1116)
D 2003-11-01T01:53:54
F Makefile.in 5cb273b7d0e945d47ee8b9ad1c2a04ce79927d2d
F Makefile.linux-gcc b86a99c493a5bfb402d1d9178dcdc4bd4b32f906
F README f1de682fbbd94899d50aca13d387d1b3fd3be2dd
F VERSION 97d209249f825001288ff942df07b48e1083af5c
@ -16,7 +16,7 @@ F doc/report1.txt a031aaf37b185e4fa540223cb516d3bccec7eeac
F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895
F libtool bbbea7d79c23323e4100103836028e4fad0d9242
F ltmain.sh abfb9387049fff6996afc6e325736597795baf11
F main.mk 6af144bac62d83899b71919c738fdf442a4f1c16
F main.mk 3e200c199e46c2b7c3106fd2c3bfa11cd0aa22c9
F publish.sh 86b5e8535830a2588f62ce1d5d1ef00e1dede23a
F spec.template a38492f1c1dd349fc24cb0565e08afc53045304b
F sqlite.1 83f4a9d37bdf2b7ef079a82d54eaf2e3509ee6ea
@ -28,10 +28,11 @@ F src/btree.h 9b7c09f1e64274d7bb74a57bbfc63778f67b1048
F src/btree_rb.c 67d154ffb0fac27a4a7eab9118ece6eaafeb49c9
F src/build.c 9def3a3b8fba59325ed686049b88c2e7aff9af12
F src/copy.c 9e47975ea96751c658bcf1a0c4f0bb7c6ee61e73
F src/date.c acb75ff7849ca923837a9d3ef6b2d3e111a32fb0
F src/delete.c 0f81e6799c089487615d38e042a2de4d2d6192bc
F src/encode.c 25ea901a9cefb3d93774afa4a06b57cb58acf544
F src/expr.c d4d8eca6363a6e680361e5d2a934b78e5c7b7fa3
F src/func.c fce558b4c1d895e81091d6d5e7d86a192fc8e84c
F src/func.c 82a749b9a03ae9834a90854464f93e29f902799b
F src/hash.c 058f077c1f36f266581aa16f907a3903abf64aa3
F src/hash.h cd0433998bc1a3759d244e1637fe5a3c13b53bf8
F src/insert.c dc200ae04a36bd36e575272a069e20c528b7fbdf
@ -49,7 +50,7 @@ F src/select.c d79ac60ba1595ff3c94b12892e87098329776482
F src/shell.c c2ba26c850874964f5ec1ebf6c43406f28e44c4a
F src/shell.tcl 27ecbd63dd88396ad16d81ab44f73e6c0ea9d20e
F src/sqlite.h.in e6cfff01fafc8a82ce82cd8c932af421dc9adb54
F src/sqliteInt.h 74dc7989c9f2b46b50485d0455a8ef8d4f178708
F src/sqliteInt.h e9b2f6b3ff315d92ee240c998f9833b82c235a71
F src/table.c 4301926464d88d2c2c7cd21c3360aa75bf068b95
F src/tclsqlite.c 3efac6b5861ac149c41251d4d4c420c94be5ba6a
F src/test1.c f9d5816610f7ec4168ab7b098d5207a5708712b6
@ -82,6 +83,7 @@ F test/btree4rb.test ae6f0438512edcb45cf483471cd6070a765963a9
F test/capi2.test ec96e0e235d87b53cbaef3d8e3e0f8ccf32c71ca
F test/conflict.test 0911bb2f079046914a6e9c3341b36658c4e2103e
F test/copy.test 88dabd4e811b17644b726aa81d404e73b7635c84
F test/date.test 17619ff81d5b813092915927c50923e265e85bd8
F test/delete.test 92256384f1801760180ded129f7427884cf28886
F test/expr.test c4cc292d601019c2f2ce95093caaa5d10284b105
F test/fkey1.test d65c824459916249bee501532d6154ddab0b5db7
@ -174,7 +176,7 @@ F www/speed.tcl 2f6b1155b99d39adb185f900456d1d592c4832b3
F www/sqlite.tcl 3c83b08cf9f18aa2d69453ff441a36c40e431604
F www/tclsqlite.tcl b9271d44dcf147a93c98f8ecf28c927307abd6da
F www/vdbe.tcl 9b9095d4495f37697fd1935d10e14c6015e80aa1
P c3a495026c7eafd576042a05a9a5f585ba8ba9b9
R 836e4a87e08b64cd5a3c29c6f69b3de9
P 181260c0aa7837feca9e415225ece0e9c4032c7a
R 879a05717d19f1ac21f75967c496c2d9
U drh
Z de7377f7ee1c2dc37770413072c96044
Z 7096cda608fb24dfdfaba056b6dd6a59

View File

@ -1 +1 @@
181260c0aa7837feca9e415225ece0e9c4032c7a
68ef9b45bd3abdedf3721011ad0fb22e8735e721

773
src/date.c Normal file
View File

@ -0,0 +1,773 @@
/*
** 2003 October 31
**
** The author disclaims copyright to this source code. In place of
** a legal notice, here is a blessing:
**
** May you do good and not evil.
** May you find forgiveness for yourself and forgive others.
** May you share freely, never taking more than you give.
**
*************************************************************************
** This file contains the C functions that implement date and time
** functions for SQLite.
**
** There is only one exported symbol in this file - the function
** sqliteRegisterDateTimeFunctions() found at the bottom of the file.
** All other code has file scope.
**
** $Id: date.c,v 1.1 2003/11/01 01:53:54 drh Exp $
**
** NOTES:
**
** SQLite processes all times and dates as Julian Day numbers. The
** dates and times are stored as the number of days since noon
** in Greenwich on November 24, 4714 B.C. according to the Gregorian
** calendar system.
**
** 1970-01-01 00:00:00 is JD 2440587.5
** 2000-01-01 00:00:00 is JD 2451544.5
**
** This implemention requires years to be expressed as a 4-digit number
** which means that only dates between 0000-01-01 and 9999-12-31 can
** be represented, even though julian day numbers allow a much wider
** range of dates.
**
** The Gregorian calendar system is used for all dates and times,
** even those that predate the Gregorian calendar. Historians usually
** use the Julian calendar for dates prior to 1582-10-15 and for some
** dates afterwards, depending on locale. Beware of this difference.
**
** The conversion algorithms are implemented based on descriptions
** in the following text:
**
** Jean Meeus
** Astronomical Algorithms, 2nd Edition, 1998
** ISBM 0-943396-61-1
** Willmann-Bell, Inc
** Richmond, Virginia (USA)
*/
#ifndef SQLITE_OMIT_DATETIME_FUNCS
#include <ctype.h>
#include <stdlib.h>
#include <assert.h>
#include "sqliteInt.h"
#include "os.h"
/*
** A structure for holding a single date and time.
*/
typedef struct DateTime DateTime;
struct DateTime {
double rJD; /* The julian day number */
int Y, M, D; /* Year, month, and day */
int h, m; /* Hour and minutes */
int tz; /* Timezone offset in minutes */
double s; /* Seconds */
char validYMD; /* True if Y,M,D are valid */
char validHMS; /* True if h,m,s are valid */
char validJD; /* True if rJD is valid */
char validTZ; /* True if tz is valid */
};
/*
** Convert N digits from zDate into an integer. Return
** -1 if zDate does not begin with N digits.
*/
static int getDigits(const char *zDate, int N){
int val = 0;
while( N-- ){
if( !isdigit(*zDate) ) return -1;
val = val*10 + *zDate - '0';
zDate++;
}
return val;
}
/*
** Read text from z[] and convert into a floating point number. Return
** the number of digits converted.
*/
static int getValue(const char *z, double *pR){
double r = 0.0;
double rDivide = 1.0;
int isNeg = 0;
int nChar = 0;
if( *z=='+' ){
z++;
nChar++;
}else if( *z=='-' ){
z++;
isNeg = 1;
nChar++;
}
if( !isdigit(*z) ) return 0;
while( isdigit(*z) ){
r = r*10.0 + *z - '0';
nChar++;
z++;
}
if( *z=='.' && isdigit(z[1]) ){
z++;
nChar++;
while( isdigit(*z) ){
r = r*10.0 + *z - '0';
rDivide *= 10.0;
nChar++;
z++;
}
r /= rDivide;
}
if( *z!=0 && !isspace(*z) ) return 0;
*pR = isNeg ? -r : r;
return nChar;
}
/*
** Parse a timezone extension on the end of a date-time.
** The extension is of the form:
**
** (+/-)HH:MM
**
** If the parse is successful, write the number of minutes
** of change in *pnMin and return 0. If a parser error occurs,
** return 0.
**
** A missing specifier is not considered an error.
*/
static int parseTimezone(const char *zDate, DateTime *p){
int sgn = 0;
int nHr, nMn;
while( isspace(*zDate) ){ zDate++; }
p->tz = 0;
if( *zDate=='-' ){
sgn = -1;
}else if( *zDate=='+' ){
sgn = +1;
}else{
return *zDate!=0;
}
zDate++;
nHr = getDigits(zDate, 2);
if( nHr<0 || nHr>14 ) return 1;
zDate += 2;
if( zDate[0]!=':' ) return 1;
zDate++;
nMn = getDigits(zDate, 2);
if( nMn<0 || nMn>59 ) return 1;
zDate += 2;
p->tz = sgn*(nMn + nHr*60);
while( isspace(*zDate) ){ zDate++; }
return *zDate!=0;
}
/*
** Parse times of the form HH:MM or HH:MM:SS or HH:MM:SS.FFFF.
** The HH, MM, and SS must each be exactly 2 digits. The
** fractional seconds FFFF can be one or more digits.
**
** Return 1 if there is a parsing error and 0 on success.
*/
static int parseHhMmSs(const char *zDate, DateTime *p){
int h, m, s;
double ms = 0.0;
h = getDigits(zDate, 2);
if( h<0 || zDate[2]!=':' ) return 1;
zDate += 3;
m = getDigits(zDate, 2);
if( m<0 || m>59 ) return 1;
zDate += 2;
if( *zDate==':' ){
s = getDigits(&zDate[1], 2);
if( s<0 || s>59 ) return 1;
zDate += 3;
if( *zDate=='.' && isdigit(zDate[1]) ){
double rScale = 1.0;
zDate++;
while( isdigit(*zDate) ){
ms = ms*10.0 + *zDate - '0';
rScale *= 10.0;
zDate++;
}
ms /= rScale;
}
}else{
s = 0;
}
p->validJD = 0;
p->validHMS = 1;
p->h = h;
p->m = m;
p->s = s + ms;
if( parseTimezone(zDate, p) ) return 1;
p->validTZ = p->tz!=0;
return 0;
}
/*
** Convert from YYYY-MM-DD HH:MM:SS to julian day. We always assume
** that the YYYY-MM-DD is according to the Gregorian calendar.
**
** Reference: Meeus page 61
*/
static void computeJD(DateTime *p){
int Y, M, D, A, B, X1, X2;
if( p->validJD ) return;
if( p->validYMD ){
Y = p->Y;
M = p->M;
D = p->D;
}else{
Y = 2000;
M = 1;
D = 1;
}
if( M<=2 ){
Y--;
M += 12;
}
A = Y/100;
B = 2 - A + (A/4);
X1 = 365.25*(Y+4716);
X2 = 30.6001*(M+1);
p->rJD = X1 + X2 + D + B - 1524.5;
p->validJD = 1;
p->validYMD = 0;
if( p->validHMS ){
p->rJD += (p->h*3600.0 + p->m*60.0 + p->s)/86400.0;
if( p->validTZ ){
p->rJD += p->tz*60/86400.0;
p->validHMS = 0;
p->validTZ = 0;
}
}
}
/*
** Parse dates of the form
**
** YYYY-MM-DD HH:MM:SS.FFF
** YYYY-MM-DD HH:MM:SS
** YYYY-MM-DD HH:MM
** YYYY-MM-DD
**
** Write the result into the DateTime structure and return 0
** on success and 1 if the input string is not a well-formed
** date.
*/
static int parseYyyyMmDd(const char *zDate, DateTime *p){
int Y, M, D;
Y = getDigits(zDate, 4);
if( Y<0 || zDate[4]!='-' ) return 1;
zDate += 5;
M = getDigits(zDate, 2);
if( M<=0 || M>12 || zDate[2]!='-' ) return 1;
zDate += 3;
D = getDigits(zDate, 2);
if( D<=0 || D>31 ) return 1;
zDate += 2;
while( isspace(*zDate) ){ zDate++; }
if( isdigit(*zDate) ){
if( parseHhMmSs(zDate, p) ) return 1;
}else if( *zDate==0 ){
p->validHMS = 0;
}else{
return 1;
}
p->validJD = 0;
p->validYMD = 1;
p->Y = Y;
p->M = M;
p->D = D;
if( p->validTZ ){
computeJD(p);
}
return 0;
}
/*
** Attempt to parse the given string into a Julian Day Number. Return
** the number of errors.
**
** The following are acceptable forms for the input string:
**
** YYYY-MM-DD HH:MM:SS.FFF +/-HH:MM
** DDDD.DD
** now
**
** In the first form, the +/-HH:MM is always optional. The fractional
** seconds extension (the ".FFF") is optional. The seconds portion
** (":SS.FFF") is option. The year and date can be omitted as long
** as there is a time string. The time string can be omitted as long
** as there is a year and date.
*/
static int parseDateOrTime(const char *zDate, DateTime *p){
int i;
memset(p, 0, sizeof(*p));
for(i=0; isdigit(zDate[i]); i++){}
if( i==4 && zDate[i]=='-' ){
return parseYyyyMmDd(zDate, p);
}else if( i==2 && zDate[i]==':' ){
return parseHhMmSs(zDate, p);
return 0;
}else if( i==0 && sqliteStrICmp(zDate,"now")==0 ){
double r;
if( sqliteOsCurrentTime(&r)==0 ){
p->rJD = r;
p->validJD = 1;
return 0;
}
return 1;
}else if( sqliteIsNumber(zDate) ){
p->rJD = atof(zDate);
p->validJD = 1;
return 0;
}
return 1;
}
/*
** Compute the Year, Month, and Day from the julian day number.
*/
static void computeYMD(DateTime *p){
int Z, A, B, C, D, E, X1;
if( p->validYMD ) return;
Z = p->rJD + 0.5;
A = (Z - 1867216.25)/36524.25;
A = Z + 1 + A - (A/4);
B = A + 1524;
C = (B - 122.1)/365.25;
D = 365.25*C;
E = (B-D)/30.6001;
X1 = 30.6001*E;
p->D = B - D - X1;
p->M = E<14 ? E-1 : E-13;
p->Y = p->M>2 ? C - 4716 : C - 4715;
p->validYMD = 1;
}
/*
** Compute the Hour, Minute, and Seconds from the julian day number.
*/
static void computeHMS(DateTime *p){
int Z, s;
if( p->validHMS ) return;
Z = p->rJD + 0.5;
s = (p->rJD + 0.5 - Z)*86400000.0 + 0.5;
p->s = 0.001*s;
s = p->s;
p->s -= s;
p->h = s/3600;
s -= p->h*3600;
p->m = s/60;
p->s += s - p->m*60;
p->validHMS = 1;
}
/*
** Process a modifier to a date-time stamp. The modifiers are
** as follows:
**
** NNN days
** NNN hours
** NNN minutes
** NNN.NNNN seconds
** NNN months
** NNN years
** start of month
** start of year
** start of week
** start of day
** weekday N
** unixepoch
**
** Return 0 on success and 1 if there is any kind of error.
*/
static int parseModifier(const char *zMod, DateTime *p){
int rc = 1;
int n;
double r;
char z[30];
for(n=0; n<sizeof(z)-1; n++){
z[n] = tolower(zMod[n]);
}
z[n] = 0;
switch( z[0] ){
case 'u': {
/*
** unixepoch
**
** Treat the current value of p->rJD as the number of
** seconds since 1970. Convert to a real julian day number.
*/
if( strcmp(z, "unixepoch")==0 && p->validJD ){
p->rJD = p->rJD/86400.0 + 2440587.5;
p->validYMD = 0;
p->validHMS = 0;
p->validTZ = 0;
rc = 0;
}
break;
}
case 'w': {
/*
** weekday N
**
** Move the date to the beginning of the next occurrance of
** weekday N where 0==Sunday, 1==Monday, and so forth. If the
** date is already on the appropriate weekday, this is equivalent
** to "start of day".
*/
if( strncmp(z, "weekday ", 8)==0 && getValue(&z[8],&r)>0
&& (n=r)==r && n>=0 && r<7 ){
int Z;
computeYMD(p);
p->validHMS = 0;
p->validTZ = 0;
p->validJD = 0;
computeJD(p);
Z = p->rJD + 1.5;
Z %= 7;
if( Z>n ) Z -= 7;
p->rJD += n - Z;
p->validYMD = 0;
p->validHMS = 0;
rc = 0;
}
break;
}
case 's': {
/*
** start of TTTTT
**
** Move the date backwards to the beginning of the current day,
** or month or year.
*/
if( strncmp(z, "start of ", 9)!=0 ) break;
zMod = &z[9];
computeYMD(p);
p->validHMS = 1;
p->h = p->m = 0;
p->s = 0.0;
p->validTZ = 0;
p->validJD = 0;
if( strcmp(zMod,"month")==0 ){
p->D = 1;
rc = 0;
}else if( strcmp(zMod,"year")==0 ){
computeYMD(p);
p->M = 1;
p->D = 1;
rc = 0;
}else if( strcmp(zMod,"day")==0 ){
rc = 0;
}
break;
}
case '+':
case '-':
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9': {
n = getValue(z, &r);
if( n<=0 ) break;
zMod = &z[n];
while( isspace(zMod[0]) ) zMod++;
n = strlen(zMod);
if( n>10 || n<3 ) break;
strcpy(z, zMod);
if( z[n-1]=='s' ){ z[n-1] = 0; n--; }
computeJD(p);
rc = 0;
if( n==3 && strcmp(z,"day")==0 ){
p->rJD += r;
}else if( n==4 && strcmp(z,"hour")==0 ){
computeJD(p);
p->rJD += r/24.0;
}else if( n==6 && strcmp(z,"minute")==0 ){
computeJD(p);
p->rJD += r/(24.0*60.0);
}else if( n==6 && strcmp(z,"second")==0 ){
computeJD(p);
p->rJD += r/(24.0*60.0*60.0);
}else if( n==5 && strcmp(z,"month")==0 ){
int x, y;
computeYMD(p);
p->M += r;
x = p->M>0 ? (p->M-1)/12 : (p->M-12)/12;
p->Y += x;
p->M -= x*12;
p->validJD = 0;
computeJD(p);
y = r;
if( y!=r ){
p->rJD += (r - y)*30.0;
}
}else if( n==4 && strcmp(z,"year")==0 ){
computeYMD(p);
p->Y += r;
p->validJD = 0;
computeJD(p);
}else{
rc = 1;
}
p->validYMD = 0;
p->validHMS = 0;
p->validTZ = 0;
break;
}
default: {
break;
}
}
return rc;
}
/*
** Process time function arguments. argv[0] is a date-time stamp.
** argv[1] and following are modifiers. Parse them all and write
** the resulting time into the DateTime structure p. Return 0
** on success and 1 if there are any errors.
*/
static int isDate(int argc, const char **argv, DateTime *p){
int i;
if( argc==0 ) return 1;
if( parseDateOrTime(argv[0], p) ) return 1;
for(i=1; i<argc; i++){
if( parseModifier(argv[i], p) ) return 1;
}
return 0;
}
/*
** The following routines implement the various date and time functions
** of SQLite.
*/
/*
** julianday( TIMESTRING, MOD, MOD, ...)
**
** Return the julian day number of the date specified in the arguments
*/
static void juliandayFunc(sqlite_func *context, int argc, const char **argv){
DateTime x;
if( isDate(argc, argv, &x)==0 ){
computeJD(&x);
sqlite_set_result_double(context, x.rJD);
}
}
/*
** datetime( TIMESTRING, MOD, MOD, ...)
**
** Return YYYY-MM-DD HH:MM:SS
*/
static void datetimeFunc(sqlite_func *context, int argc, const char **argv){
DateTime x;
if( isDate(argc, argv, &x)==0 ){
char zBuf[100];
computeYMD(&x);
computeHMS(&x);
sprintf(zBuf, "%04d-%02d-%02d %02d:%02d:%02d",x.Y, x.M, x.D, x.h, x.m,
(int)(x.s));
sqlite_set_result_string(context, zBuf, -1);
}
}
/*
** time( TIMESTRING, MOD, MOD, ...)
**
** Return HH:MM:SS
*/
static void timeFunc(sqlite_func *context, int argc, const char **argv){
DateTime x;
if( isDate(argc, argv, &x)==0 ){
char zBuf[100];
computeHMS(&x);
sprintf(zBuf, "%02d:%02d:%02d", x.h, x.m, (int)x.s);
sqlite_set_result_string(context, zBuf, -1);
}
}
/*
** date( TIMESTRING, MOD, MOD, ...)
**
** Return YYYY-MM-DD
*/
static void dateFunc(sqlite_func *context, int argc, const char **argv){
DateTime x;
if( isDate(argc, argv, &x)==0 ){
char zBuf[100];
computeYMD(&x);
sprintf(zBuf, "%04d-%02d-%02d", x.Y, x.M, x.D);
sqlite_set_result_string(context, zBuf, -1);
}
}
/*
** strftime( FORMAT, TIMESTRING, MOD, MOD, ...)
**
** Return a string described by FORMAT. Conversions as follows:
**
** %d day of month
** %f ** fractional seconds SS.SSS
** %H hour 00-24
** %j day of year 000-366
** %J ** Julian day number
** %m month 01-12
** %M minute 00-59
** %s seconds since 1970-01-01
** %S seconds 00-59
** %w day of week 0-6 sunday==0
** %W week of year 00-53
** %Y year 0000-9999
** %% %
*/
static void strftimeFunc(sqlite_func *context, int argc, const char **argv){
DateTime x;
int n, i, j;
char *z;
const char *zFmt = argv[0];
char zBuf[100];
if( isDate(argc-1, argv+1, &x) ) return;
for(i=0, n=1; zFmt[i]; i++, n++){
if( zFmt[i]=='%' ){
switch( zFmt[i+1] ){
case 'd':
case 'H':
case 'm':
case 'M':
case 'S':
case 'W':
n++;
/* fall thru */
case 'w':
case '%':
break;
case 'f':
n += 8;
break;
case 'j':
n += 3;
break;
case 'Y':
n += 8;
break;
case 's':
case 'J':
n += 50;
break;
default:
return; /* ERROR. return a NULL */
}
i++;
}
}
if( n<sizeof(zBuf) ){
z = zBuf;
}else{
z = sqliteMalloc( n );
if( z==0 ) return;
}
computeJD(&x);
computeYMD(&x);
computeHMS(&x);
for(i=j=0; zFmt[i]; i++){
if( zFmt[i]!='%' ){
z[j++] = zFmt[i];
}else{
i++;
switch( zFmt[i] ){
case 'd': sprintf(&z[j],"%02d",x.D); j+=2; break;
case 'f': {
int s = x.s;
int ms = (x.s - s)*1000.0;
sprintf(&z[j],"%02d.%03d",s,ms);
j += strlen(&z[j]);
break;
}
case 'H': sprintf(&z[j],"%02d",x.h); j+=2; break;
case 'W': /* Fall thru */
case 'j': {
int n;
DateTime y = x;
y.validJD = 0;
y.M = 1;
y.D = 1;
computeJD(&y);
n = x.rJD - y.rJD + 1;
if( zFmt[i]=='W' ){
sprintf(&z[j],"%02d",(n+6)/7);
j += 2;
}else{
sprintf(&z[j],"%03d",n);
j += 3;
}
break;
}
case 'J': sprintf(&z[j],"%.16g",x.rJD); j+=strlen(&z[j]); break;
case 'm': sprintf(&z[j],"%02d",x.M); j+=2; break;
case 'M': sprintf(&z[j],"%02d",x.m); j+=2; break;
case 's': {
sprintf(&z[j],"%d",(int)((x.rJD-2440587.5)*86400.0));
j += strlen(&z[j]);
break;
}
case 'S': sprintf(&z[j],"%02d",(int)x.s); j+=2; break;
case 'w': z[j++] = (((int)(x.rJD+1.5)) % 7) + '0'; break;
case 'Y': sprintf(&z[j],"%04d",x.Y); j+=strlen(&z[j]); break;
case '%': z[j++] = '%'; break;
}
}
}
z[j] = 0;
sqlite_set_result_string(context, z, -1);
if( z!=zBuf ){
sqliteFree(z);
}
}
#endif /* !defined(SQLITE_OMIT_DATETIME_FUNCS) */
/*
** This function registered all of the above C functions as SQL
** functions. This should be the only routine in this file with
** external linkage.
*/
void sqliteRegisterDateTimeFunctions(sqlite *db){
static struct {
char *zName;
int nArg;
int dataType;
void (*xFunc)(sqlite_func*,int,const char**);
} aFuncs[] = {
#ifndef SQLITE_OMIT_DATETIME_FUNCS
{ "julianday", -1, SQLITE_NUMERIC, juliandayFunc },
{ "date", -1, SQLITE_TEXT, dateFunc },
{ "time", 1, SQLITE_TEXT, timeFunc },
{ "datetime", -1, SQLITE_TEXT, datetimeFunc },
{ "strftime", -1, SQLITE_TEXT, strftimeFunc },
#endif
};
int i;
for(i=0; i<sizeof(aFuncs)/sizeof(aFuncs[0]); i++){
sqlite_create_function(db, aFuncs[i].zName,
aFuncs[i].nArg, aFuncs[i].xFunc, 0);
if( aFuncs[i].xFunc ){
sqlite_function_type(db, aFuncs[i].zName, aFuncs[i].dataType);
}
}
}

View File

@ -16,7 +16,7 @@
** sqliteRegisterBuildinFunctions() found at the bottom of the file.
** All other code has file scope.
**
** $Id: func.c,v 1.32 2003/10/10 02:09:57 drh Exp $
** $Id: func.c,v 1.33 2003/11/01 01:53:54 drh Exp $
*/
#include <ctype.h>
#include <math.h>
@ -539,388 +539,6 @@ static void minMaxFinalize(sqlite_func *context){
}
}
/****************************************************************************
** Time and date functions.
**
** 1970-01-01 00:00:00 is JD 2440587.5.
** 2000-01-01 00:00:00 is JD 2451544.5
**
** SQLite processes all times and dates as Julian Day numbers. The
** dates and times are stored as the number of days since noon
** in Greenwich on November 24, 4714 B.C. according to the Gregorian
** calendar system.
**
** This implement requires years to be expressed as a 4-digit number
** which means that only dates between 0000-01-01 and 9999-12-31 can
** be represented, even though julian day numbers allow a much wider
** range of dates.
**
** The Gregorian calendar system is used for all dates and times,
** even those that predate the Gregorian calendar. Historians usually
** use the Julian calendar for dates prior to 1582-10-15 and for some
** dates afterwards, depending on locale. Beware of this difference.
**
** The conversion algorithms are implemented based on descriptions
** in the following text:
**
** Jean Meeus
** Astronomical Algorithms, 2nd Edition, 1998
** ISBM 0-943396-61-1
** Willmann-Bell, Inc
** Richmond, Virginia (USA)
*/
#ifndef SQLITE_OMIT_DATETIME_FUNCS
/*
** Convert N digits from zDate into an integer. Return
** -1 if zDate does not begin with N digits.
*/
static int getDigits(const char *zDate, int N){
int val = 0;
while( N-- ){
if( !isdigit(*zDate) ) return -1;
val = val*10 + *zDate - '0';
zDate++;
}
return val;
}
/*
** Parse a timezone extension on the end of a datetime stamp.
** The extension is of the form:
**
** (+/-)HH:MM
**
** If the parse is successful, write the number of minutes
** of change in *pnMin and return 0. If a parser error occurs,
** return 0.
**
** A missing specifier is not considered an error.
*/
static int parseTimezone(const char *zDate, int *pnMin){
int sgn = 0;
int nHr, nMn;
while( isspace(*zDate) ){ zDate++; }
*pnMin = 0;
if( *zDate=='-' ){
sgn = -1;
}else if( *zDate=='+' ){
sgn = +1;
}else{
return *zDate!=0;
}
zDate++;
nHr = getDigits(zDate, 2);
if( nHr<0 || nHr>14 ) return 1;
zDate += 2;
if( zDate[0]!=':' ) return 1;
zDate++;
nMn = getDigits(zDate, 2);
if( nMn<0 || nMn>59 ) return 1;
zDate += 2;
*pnMin = sgn*(nMn + nHr*60);
while( isspace(*zDate) ){ *zDate++; }
return *zDate!=0;
}
/*
** Parse times of the form HH:MM or HH:MM:SS or HH:MM:SS.FFFF.
** The HH, MM, and SS must each be exactly 2 digits. The
** fractional seconds FFFF can be one or more digits.
**
** The time string can be followed by an optional timezone specifier
** of the following form: (+/-)HH:MM.
**
** Whatever the format, the string is converted into a julian
** day number and stored in *prJD.
**
** Return 1 if there is a parsing error and 0 on success.
*/
static int parseHhMmSs(const char *zDate, double *prJD){
int h, m, s, tz;
double ms = 0.0;
h = getDigits(zDate, 2);
if( h<0 || zDate[2]!=':' ) return 1;
zDate += 3;
m = getDigits(zDate, 2);
if( m<0 || m>59 ) return 1;
zDate += 2;
if( *zDate==':' ){
s = getDigits(&zDate[1], 2);
if( s<0 || s>59 ) return 1;
zDate += 3;
if( *zDate=='.' && isdigit(zDate[1]) ){
double rScale = 1.0/864000.0;
zDate++;
while( isdigit(*zDate) ){
ms += rScale * (*zDate - '0');
rScale *= 0.1;
zDate++;
}
}
}else{
s = 0;
}
if( parseTimezone(zDate, &tz) ) return 1;
*prJD = (h*3600.0 + (m+tz)*60.0 + s)/86400.0 + ms;
return 0;
}
/*
** Parse dates of the form
**
** YYYY-MM-DD HH:MM:SS
** YYYY-MM-DD HH:MM
** YYYY-MM-DD
**
** Write the result as a julian day number in *prJD. Return 0
** on success and 1 if the input string is not a well-formed
** date.
*/
static int parseYyyyMmDd(const char *zDate, double *prJD){
int Y, M, D;
double rTime;
int A, B, X1, X2;
Y = getDigits(zDate, 4);
if( Y<0 || zDate[4]!='-' ) return 1;
zDate += 5;
M = getDigits(zDate, 2);
if( M<=0 || M>12 || zDate[2]!='-' ) return 1;
zDate += 3;
D = getDigits(zDate, 2);
if( D<=0 || D>31 ) return 1;
zDate += 2;
while( isspace(*zDate) ){ zDate++; }
if( isdigit(*zDate) ){
if( parseHhMmSs(zDate, &rTime) ) return 1;
}else if( *zDate==0 ){
rTime = 0.0;
}else{
return 1;
}
/* The year, month, and day are now stored in Y, M, and D. Convert
** these into the Julian Day number. See Meeus page 61.
*/
if( M<=2 ){
Y--;
M += 12;
}
A = Y/100;
B = 2 - A + (A/4);
X1 = 365.25*(Y+4716);
X2 = 30.6001*(M+1);
*prJD = X1 + X2 + D + B - 1524.5 + rTime;
return 0;
}
/*
** Attempt to parse the given string into a Julian Day Number. Return
** the number of errors.
**
** The following are acceptable forms for the input string:
**
** YYYY-MM-DD HH:MM:SS.FFF +/-HH:MM
** DDDD.DD
** now
**
** In the first form, the +/-HH:MM is always optional. The fractional
** seconds extension (the ".FFF") is optional. The seconds portion
** (":SS.FFF") is option. The year and date can be omitted as long
** as there is a time string. The time string can be omitted as long
** as there is a year and date.
**
** If the bRelative flag is set and the format is HH:MM or HH:MM:SS then
** make the result is relative to midnight instead of noon. In other words,
** if bRelative is true, "00:00:00" parses to 0.0 but if bRelative is
** false, "00:00:00" parses to 0.5.
*/
static int parseDateOrTime(const char *zDate, int bRelative, double *prJD){
int i;
for(i=0; isdigit(zDate[i]); i++){}
if( i==4 && zDate[i]=='-' ){
return parseYyyyMmDd(zDate, prJD);
}else if( i==2 && zDate[i]==':' ){
if( parseHhMmSs(zDate, prJD) ) return 1;
if( !bRelative ) *prJD += 2451544.5;
return 0;
}else if( i==0 && sqliteStrICmp(zDate,"now")==0 ){
return sqliteOsCurrentTime(prJD);
}else if( sqliteIsNumber(zDate) ){
*prJD = atof(zDate);
return 0;
}
return 1;
}
/*
** A structure for holding date and time.
*/
typedef struct DateTime DateTime;
struct DateTime {
double rJD; /* The julian day number */
int Y, M, D; /* Year, month, and day */
int h, m; /* Hour and minutes */
double s; /* Seconds */
};
/*
** Break up a julian day number into year, month, day, hour, minute, second.
** This function assume the Gregorian calendar - even for dates prior
** to the invention of the Gregorian calendar in 1582.
**
** See Meeus page 63.
**
** If mode==1 only the year, month, and day are computed. If mode==2
** then only the hour, minute, and second are computed. If mode==3 then
** everything is computed. If mode==0, this routine is a no-op.
*/
static void decomposeDate(DateTime *p, int mode){
int Z;
Z = p->rJD + 0.5;
if( mode & 1 ){
int A, B, C, D, E, X1;
A = (Z - 1867216.25)/36524.25;
A = Z + 1 + A - (A/4);
B = A + 1524;
C = (B - 122.1)/365.25;
D = 365.25*C;
E = (B-D)/30.6001;
X1 = 30.6001*E;
p->D = B - D - X1;
p->M = E<14 ? E-1 : E-13;
p->Y = p->M>2 ? C - 4716 : C - 4715;
}
if( mode & 2 ){
int s = (p->rJD + 0.5 - Z)*86400000.0 + 0.5;
p->s = 0.001*s;
s = p->s;
p->s -= s;
p->h = s/3600;
s -= p->h*3600;
p->m = s/60;
p->s += s - p->m*60;
}
}
/*
** Check to see that all arguments are valid date strings. If any
** argument is not a valid date string, return 0. If all arguments
** are valid, return 1 and write into *p->rJD the sum of the julian day
** numbers for all date strings.
**
** A "valid" date string is one that is accepted by parseDateOrTime().
**
** The mode argument is passed through to decomposeDate() in order to
** fill in the year, month, day, hour, minute, and second of the *p
** structure, if desired.
*/
static int isDate(int argc, const char **argv, DateTime *p, int mode){
double r;
int i;
p->rJD = 0.0;
for(i=0; i<argc; i++){
if( argv[i]==0 ) return 0;
if( parseDateOrTime(argv[i], i, &r) ) return 0;
p->rJD += r;
}
decomposeDate(p, mode);
return 1;
}
/*
** The following routines implement the various date and time functions
** of SQLite.
*/
static void juliandayFunc(sqlite_func *context, int argc, const char **argv){
DateTime x;
if( isDate(argc, argv, &x, 0) ){
sqlite_set_result_double(context, x.rJD);
}
}
static void timestampFunc(sqlite_func *context, int argc, const char **argv){
DateTime x;
if( isDate(argc, argv, &x, 3) ){
char zBuf[100];
sprintf(zBuf, "%04d-%02d-%02d %02d:%02d:%02d",x.Y, x.M, x.D, x.h, x.m,
(int)(x.s+0.5));
sqlite_set_result_string(context, zBuf, -1);
}
}
static void timeFunc(sqlite_func *context, int argc, const char **argv){
DateTime x;
if( isDate(argc, argv, &x, 2) ){
char zBuf[100];
sprintf(zBuf, "%02d:%02d:%02d", x.h, x.m, (int)(x.s+0.5));
sqlite_set_result_string(context, zBuf, -1);
}
}
static void dateFunc(sqlite_func *context, int argc, const char **argv){
DateTime x;
if( isDate(argc, argv, &x, 1) ){
char zBuf[100];
sprintf(zBuf, "%04d-%02d-%02d", x.Y, x.M, x.D);
sqlite_set_result_string(context, zBuf, -1);
}
}
static void yearFunc(sqlite_func *context, int argc, const char **argv){
DateTime x;
if( isDate(argc, argv, &x, 1) ){
sqlite_set_result_int(context, x.Y);
}
}
static void monthFunc(sqlite_func *context, int argc, const char **argv){
DateTime x;
if( isDate(argc, argv, &x, 1) ){
sqlite_set_result_int(context, x.M);
}
}
static void dayofweekFunc(sqlite_func *context, int argc, const char **argv){
DateTime x;
if( isDate(argc, argv, &x, 0) ){
int Z = x.rJD + 1.5;
sqlite_set_result_int(context, Z % 7);
}
}
static void dayofmonthFunc(sqlite_func *context, int argc, const char **argv){
DateTime x;
if( isDate(argc, argv, &x, 1) ){
sqlite_set_result_int(context, x.D);
}
}
static void secondFunc(sqlite_func *context, int argc, const char **argv){
DateTime x;
if( isDate(argc, argv, &x, 2) ){
sqlite_set_result_double(context, x.s);
}
}
static void minuteFunc(sqlite_func *context, int argc, const char **argv){
DateTime x;
if( isDate(argc, argv, &x, 2) ){
sqlite_set_result_int(context, x.m);
}
}
static void hourFunc(sqlite_func *context, int argc, const char **argv){
DateTime x;
if( isDate(argc, argv, &x, 2) ){
sqlite_set_result_int(context, x.h);
}
}
static void unixToJdFunc(sqlite_func *context, int argc, const char **argv){
sqlite_set_result_double(context, atof(argv[0])/(24.0*3600.0)+2440587.5);
}
static void unixtimeFunc(sqlite_func *context, int argc, const char **argv){
DateTime x;
if( isDate(argc, argv, &x, 0) ){
sqlite_set_result_double(context, (x.rJD-2440587.5)*24.0*3600.0);
}
}
#endif /* !defined(SQLITE_OMIT_DATETIME_FUNCS) */
/***************************************************************************/
/*
** This function registered all of the above C functions as SQL
** functions. This should be the only routine in this file with
@ -954,21 +572,6 @@ void sqliteRegisterBuiltinFunctions(sqlite *db){
{ "nullif", 2, SQLITE_ARGS, nullifFunc },
{ "sqlite_version",0,SQLITE_TEXT, versionFunc},
{ "quote", 1, SQLITE_ARGS, quoteFunc },
#ifndef SQLITE_OMIT_DATETIME_FUNCS
{ "julianday", -1, SQLITE_NUMERIC, juliandayFunc },
{ "unixtime", -1, SQLITE_NUMERIC, unixtimeFunc },
{ "unix_to_jd", 1, SQLITE_NUMERIC, unixToJdFunc },
{ "timestamp", -1, SQLITE_TEXT, timestampFunc },
{ "time", -1, SQLITE_TEXT, timeFunc },
{ "date", -1, SQLITE_TEXT, dateFunc },
{ "year", -1, SQLITE_NUMERIC, yearFunc },
{ "month", -1, SQLITE_NUMERIC, monthFunc },
{ "dayofmonth",-1, SQLITE_NUMERIC, dayofmonthFunc },
{ "dayofweek", -1, SQLITE_NUMERIC, dayofweekFunc },
{ "hour", -1, SQLITE_NUMERIC, hourFunc },
{ "minute", -1, SQLITE_NUMERIC, minuteFunc },
{ "second", -1, SQLITE_NUMERIC, secondFunc },
#endif
#ifdef SQLITE_SOUNDEX
{ "soundex", 1, SQLITE_TEXT, soundexFunc},
#endif
@ -1010,4 +613,5 @@ void sqliteRegisterBuiltinFunctions(sqlite *db){
aAggs[i].nArg, aAggs[i].xStep, aAggs[i].xFinalize, 0);
sqlite_function_type(db, aAggs[i].zName, aAggs[i].dataType);
}
sqliteRegisterDateTimeFunctions(db);
}

View File

@ -11,7 +11,7 @@
*************************************************************************
** Internal interface definitions for SQLite.
**
** @(#) $Id: sqliteInt.h,v 1.200 2003/10/18 09:37:26 danielk1977 Exp $
** @(#) $Id: sqliteInt.h,v 1.201 2003/11/01 01:53:54 drh Exp $
*/
#include "config.h"
#include "sqlite.h"
@ -1159,6 +1159,7 @@ IdList *sqliteIdListDup(IdList*);
Select *sqliteSelectDup(Select*);
FuncDef *sqliteFindFunction(sqlite*,const char*,int,int,int);
void sqliteRegisterBuiltinFunctions(sqlite*);
void sqliteRegisterDateTimeFunctions(sqlite*);
int sqliteSafetyOn(sqlite*);
int sqliteSafetyOff(sqlite*);
int sqliteSafetyCheck(sqlite*);

123
test/date.test Normal file
View File

@ -0,0 +1,123 @@
# 2003 October 31
#
# The author disclaims copyright to this source code. In place of
# a legal notice, here is a blessing:
#
# May you do good and not evil.
# May you find forgiveness for yourself and forgive others.
# May you share freely, never taking more than you give.
#
#***********************************************************************
# This file implements regression tests for SQLite library. The
# focus of this file is testing date and time functions.
#
# $Id: date.test,v 1.1 2003/11/01 01:53:54 drh Exp $
set testdir [file dirname $argv0]
source $testdir/tester.tcl
proc datetest {tnum expr result} {
do_test date-$tnum [subst {
execsql "SELECT coalesce($expr,'NULL')"
}] [list $result]
}
datetest 1.1 julianday('2000-01-01') 2451544.5
datetest 1.2 julianday('1970-01-01') 2440587.5
datetest 1.3 julianday('1910-04-20') 2418781.5
datetest 1.4 julianday('1986-02-09') 2446470.5
datetest 1.5 julianday('12:00:00') 2451545
datetest 1.6 {julianday('2000-01-01 12:00:00')} 2451545
datetest 1.7 {julianday('2000-01-01 12:00')} 2451545
datetest 1.8 julianday('bogus') NULL
datetest 1.9 julianday('1999-12-31') 2451543.5
datetest 1.10 julianday('1999-12-32') NULL
datetest 1.11 julianday('1999-13-01') NULL
datetest 1.12 julianday('2003-02-31') 2452701.5
datetest 1.13 julianday('2003-03-03') 2452701.5
datetest 1.14 julianday('+2000-01-01') NULL
datetest 1.15 julianday('200-01-01') NULL
datetest 1.16 julianday('2000-1-01') NULL
datetest 1.17 julianday('2000-01-1') NULL
datetest 1.18 {julianday('2000-01-01 12:00:00')} 2451545
datetest 1.19 {julianday('2000-01-01 12:00:00.1')} 2451545.00000116
datetest 1.20 {julianday('2000-01-01 12:00:00.01')} 2451545.00000012
datetest 1.21 {julianday('2000-01-01 12:00:00.001')} 2451545.00000001
datetest 1.22 {julianday('2000-01-01 12:00:00.')} NULL
datetest 1.23 julianday(12345.6) 12345.6
datetest 1.24 {julianday('2001-01-01 12:00:00 bogus')} NULL
datetest 1.25 {julianday('2001-01-01 bogus')} NULL
datetest 2.1 datetime(0,'unixepoch') {1970-01-01 00:00:00}
datetest 2.2 datetime(946684800,'unixepoch') {2000-01-01 00:00:00}
datetest 2.3 {date('2003-10-22','weekday 0')} 2003-10-26
datetest 2.4 {date('2003-10-22','weekday 1')} 2003-10-27
datetest 2.5 {date('2003-10-22','weekday 2')} 2003-10-28
datetest 2.6 {date('2003-10-22','weekday 3')} 2003-10-22
datetest 2.7 {date('2003-10-22','weekday 4')} 2003-10-23
datetest 2.8 {date('2003-10-22','weekday 5')} 2003-10-24
datetest 2.9 {date('2003-10-22','weekday 6')} 2003-10-25
datetest 2.10 {date('2003-10-22','weekday 7')} NULL
datetest 2.11 {date('2003-10-22','weekday 5.5')} NULL
datetest 2.12 {datetime('2003-10-22 12:34','weekday 0')} {2003-10-26 00:00:00}
datetest 2.13 {datetime('2003-10-22 12:34','start of month')} \
{2003-10-01 00:00:00}
datetest 2.14 {datetime('2003-10-22 12:34','start of year')} \
{2003-01-01 00:00:00}
datetest 2.15 {datetime('2003-10-22 12:34','start of day')} \
{2003-10-22 00:00:00}
datetest 2.16 time('12:34:56.43') 12:34:56
datetest 2.17 {datetime('2003-10-22 12:34','1 day')} {2003-10-23 12:34:00}
datetest 2.18 {datetime('2003-10-22 12:34','+1 day')} {2003-10-23 12:34:00}
datetest 2.19 {datetime('2003-10-22 12:34','+1.25 day')} {2003-10-23 18:34:00}
datetest 2.20 {datetime('2003-10-22 12:34','-1.0 day')} {2003-10-21 12:34:00}
datetest 2.21 {datetime('2003-10-22 12:34','1 month')} {2003-11-22 12:34:00}
datetest 2.22 {datetime('2003-10-22 12:34','11 month')} {2004-09-22 12:34:00}
datetest 2.23 {datetime('2003-10-22 12:34','-13 month')} {2002-09-22 12:34:00}
datetest 2.24 {datetime('2003-10-22 12:34','1.5 months')} {2003-12-07 12:34:00}
datetest 2.25 {datetime('2003-10-22 12:34','-5 years')} {1998-10-22 12:34:00}
datetest 2.26 {datetime('2003-10-22 12:34','+10.5 minutes')} \
{2003-10-22 12:44:30}
datetest 2.27 {datetime('2003-10-22 12:34','-1.25 hours')} \
{2003-10-22 11:19:00}
datetest 2.28 {datetime('2003-10-22 12:34','11.25 seconds')} \
{2003-10-22 12:34:11}
datetest 2.29 {datetime('2003-10-22 12:24','+5 bogus')} NULL
datetest 3.1 {strftime('%d','2003-10-31 12:34:56.432')} 31
datetest 3.2 {strftime('%f','2003-10-31 12:34:56.432')} 56.432
datetest 3.3 {strftime('%H','2003-10-31 12:34:56.432')} 12
datetest 3.4 {strftime('%j','2003-10-31 12:34:56.432')} 304
datetest 3.5 {strftime('%J','2003-10-31 12:34:56.432')} 2452944.024264259
datetest 3.6 {strftime('%m','2003-10-31 12:34:56.432')} 10
datetest 3.7 {strftime('%M','2003-10-31 12:34:56.432')} 34
datetest 3.8 {strftime('%s','2003-10-31 12:34:56.432')} 1067603696
datetest 3.9 {strftime('%S','2003-10-31 12:34:56.432')} 56
datetest 3.10 {strftime('%w','2003-10-31 12:34:56.432')} 5
datetest 3.11 {strftime('%W','2003-10-31 12:34:56.432')} 44
datetest 3.12 {strftime('%Y','2003-10-31 12:34:56.432')} 2003
datetest 3.13 {strftime('%%','2003-10-31 12:34:56.432')} %
datetest 3.14 {strftime('%_','2003-10-31 12:34:56.432')} NULL
datetest 3.15 {strftime('%Y-%m-%d','2003-10-31')} 2003-10-31
proc repeat {n txt} {
set x {}
while {$n>0} {
append x $txt
incr n -1
}
return $x
}
datetest 3.16 "strftime('[repeat 200 %Y]','2003-10-31')" [repeat 200 2003]
datetest 3.17 "strftime('[repeat 200 abc%m123]','2003-10-31')" \
[repeat 200 abc10123]
set now [clock format [clock seconds] -format "%Y-%m-%d" -gmt 1]
datetest 4.1 {date('now')} $now
datetest 5.1 {datetime('1994-04-16 14:00:00 -05:00')} {1994-04-16 09:00:00}
datetest 5.2 {datetime('1994-04-16 14:00:00 +05:15')} {1994-04-16 19:15:00}
datetest 5.3 {datetime('1994-04-16 05:00:00 -08:30')} {1994-04-15 20:30:00}
datetest 5.4 {datetime('1994-04-16 14:00:00 +11:55')} {1994-04-17 01:55:00}
finish_test