]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Convert the date/time functions to work with milliseconds since the
authordrh <drh@noemail.net>
Thu, 12 Jun 2008 16:35:38 +0000 (16:35 +0000)
committerdrh <drh@noemail.net>
Thu, 12 Jun 2008 16:35:38 +0000 (16:35 +0000)
julian epoch internally (instead of days since the epoch) in order to avoid
problems with floating-point roundoff error.  The interface is unchanged. (CVS 5215)

FossilOrigin-Name: ed35f8a98323dadb64b423615287fb24ea262ffb

manifest
manifest.uuid
src/date.c
test/date.test

index 778868816db754546c08449c1e2dd36afe7326aa..56390ef438bd29648e491db2cfe53ffeb0ed893d 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Add\sanother\stest\sto\sincrblob2.test.\sThis\stest\sfailed\sto\sreveal\sany\snew\sbugs.\s(CVS\s5214)
-D 2008-06-12T14:42:07
+C Convert\sthe\sdate/time\sfunctions\sto\swork\swith\smilliseconds\ssince\sthe\njulian\sepoch\sinternally\s(instead\sof\sdays\ssince\sthe\sepoch)\sin\sorder\sto\savoid\nproblems\swith\sfloating-point\sroundoff\serror.\s\sThe\sinterface\sis\sunchanged.\s(CVS\s5215)
+D 2008-06-12T16:35:38
 F Makefile.arm-wince-mingw32ce-gcc ac5f7b2cef0cd850d6f755ba6ee4ab961b1fadf7
 F Makefile.in ce92ea8dc7adfb743757794f51c10d1b0d9c55e4
 F Makefile.linux-gcc d53183f4aa6a9192d249731c90dbdffbd2c68654
@@ -102,7 +102,7 @@ F src/btreeInt.h dc04ee33d8eb84714b2acdf81336fbbf6e764530
 F src/build.c a52d9d51341444a2131e3431608f245db80d9591
 F src/callback.c 77b302b0d41468dcda78c70e706e5b84577f0fa0
 F src/complete.c 4cf68fd75d60257524cbe74f87351b9848399131
-F src/date.c 95d742c3fbbcccb3bf89e1acfaf90c9626e37592
+F src/date.c 76a5ba94772346efedf0c412c47e67891bbb266c
 F src/delete.c d3fc5987f2eb88f7b9549d58a5dfea079a83fe8b
 F src/expr.c ecb3b23d3543427cba3e2ac12a6c6ae4bb20d39b
 F src/fault.c 1f6177188edb00641673e462f3fab8cba9f7422b
@@ -257,7 +257,7 @@ F test/crash7.test e20a7b9ee1d16eaef7c94a4cb7ed2191b4d05970
 F test/crashtest1.c 09c1c7d728ccf4feb9e481671e29dda5669bbcc2
 F test/createtab.test 199cf68f44e5d9e87a0b8afc7130fdeb4def3272
 F test/cse.test 4b8a49decaefccb835ecc67249277be491713f6c
-F test/date.test fe0afe5d96fa5016ea8f9fd99ad9b89312974462
+F test/date.test 6ddaefb613aa728e281992da3560de3d88fb0aeb
 F test/default.test 252298e42a680146b1dd64f563b95bdf088d94fb
 F test/delete.test f171c1011395a8dd63169438fe1d8cc625eb7442
 F test/delete2.test c06be3806ba804bc8c6f134476816080280b40e3
@@ -593,7 +593,7 @@ F tool/speedtest16.c c8a9c793df96db7e4933f0852abb7a03d48f2e81
 F tool/speedtest2.tcl ee2149167303ba8e95af97873c575c3e0fab58ff
 F tool/speedtest8.c 1dbced29de5f59ba2ebf877edcadf171540374d1
 F tool/speedtest8inst1.c 293327bc76823f473684d589a8160bde1f52c14e
-P 6ec4d7653b1e67ba0951e909ee23fe774762d646
-R 570f09991125bd50f45eef8fb2b56b3d
-U danielk1977
-Z b8c281924d7fa5a6eb236e8528b6194f
+P 20d8ea45afcddf22632c3de984147826d0616d3f
+R 0304989bcb11c8650d732a0c70396d11
+U drh
+Z 15805328e436510985375f9b6e038fc3
index d91903ab9c3fa194acce08af99b35206b07c63c3..3e2682f2ff808693e627c4697f85b986e3610a72 100644 (file)
@@ -1 +1 @@
-20d8ea45afcddf22632c3de984147826d0616d3f
\ No newline at end of file
+ed35f8a98323dadb64b423615287fb24ea262ffb
\ No newline at end of file
index 5d70af98c21f935cc5afdd6e341cbd84fdf79b7d..f258d5cfd9947c1c3f1e31b95e38f69a6d13ee65 100644 (file)
@@ -16,7 +16,7 @@
 ** sqlite3RegisterDateTimeFunctions() found at the bottom of the file.
 ** All other code has file scope.
 **
-** $Id: date.c,v 1.82 2008/06/12 13:50:00 drh Exp $
+** $Id: date.c,v 1.83 2008/06/12 16:35:38 drh Exp $
 **
 ** SQLite processes all times and dates as Julian Day numbers.  The
 ** dates and times are stored as the number of days since noon
 */
 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 */
+  sqlite3_int64 iJD; /* The julian day number times 86400000 */
+  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 iJD is valid */
+  char validTZ;      /* True if tz is valid */
 };
 
 
@@ -256,12 +256,12 @@ static void computeJD(DateTime *p){
   B = 2 - A + (A/4);
   X1 = 365.25*(Y+4716);
   X2 = 30.6001*(M+1);
-  p->rJD = X1 + X2 + D + B - 1524.5;
+  p->iJD = (X1 + X2 + D + B - 1524.5)*86400000;
   p->validJD = 1;
   if( p->validHMS ){
-    p->rJD += (p->h*3600.0 + p->m*60.0 + p->s)/86400.0;
+    p->iJD += p->h*3600000 + p->m*60000 + p->s*1000;
     if( p->validTZ ){
-      p->rJD -= p->tz*60/86400.0;
+      p->iJD -= p->tz*60000;
       p->validYMD = 0;
       p->validHMS = 0;
       p->validTZ = 0;
@@ -320,7 +320,7 @@ static void setDateTimeToCurrent(sqlite3_context *context, DateTime *p){
   double r;
   sqlite3 *db = sqlite3_context_db_handle(context);
   sqlite3OsCurrentTime(db->pVfs, &r);
-  p->rJD = r;
+  p->iJD = (sqlite3_int64)(r*86400000.0 + 0.5);
   p->validJD = 1;
 }
 
@@ -345,7 +345,6 @@ static int parseDateOrTime(
   const char *zDate, 
   DateTime *p
 ){
-  memset(p, 0, sizeof(*p));
   if( parseYyyyMmDd(zDate,p)==0 ){
     return 0;
   }else if( parseHhMmSs(zDate, p)==0 ){
@@ -354,7 +353,9 @@ static int parseDateOrTime(
     setDateTimeToCurrent(context, p);
     return 0;
   }else if( sqlite3IsNumber(zDate, 0, SQLITE_UTF8) ){
-    getValue(zDate, &p->rJD);
+    double r;
+    getValue(zDate, &r);
+    p->iJD = (sqlite3_int64)(r*86400000.0 + 0.5);
     p->validJD = 1;
     return 0;
   }
@@ -372,7 +373,7 @@ static void computeYMD(DateTime *p){
     p->M = 1;
     p->D = 1;
   }else{
-    Z = p->rJD + 0.5;
+    Z = (p->iJD + 43200000)/86400000;
     A = (Z - 1867216.25)/36524.25;
     A = Z + 1 + A - (A/4);
     B = A + 1524;
@@ -391,12 +392,11 @@ static void computeYMD(DateTime *p){
 ** Compute the Hour, Minute, and Seconds from the julian day number.
 */
 static void computeHMS(DateTime *p){
-  int Z, s;
+  int s;
   if( p->validHMS ) return;
   computeJD(p);
-  Z = p->rJD + 0.5;
-  s = (p->rJD + 0.5 - Z)*86400000.0 + 0.5;
-  p->s = 0.001*s;
+  s = (p->iJD + 43200000) % 86400000;
+  p->s = s/1000.0;
   s = p->s;
   p->s -= s;
   p->h = s/3600;
@@ -425,10 +425,11 @@ static void clearYMD_HMS_TZ(DateTime *p){
 
 #ifndef SQLITE_OMIT_LOCALTIME
 /*
-** Compute the difference (in days) between localtime and UTC (a.k.a. GMT)
+** Compute the difference (in milliseconds)
+** between localtime and UTC (a.k.a. GMT)
 ** for the time value p where p is in UTC.
 */
-static double localtimeOffset(DateTime *p){
+static int localtimeOffset(DateTime *p){
   DateTime x, y;
   time_t t;
   x = *p;
@@ -447,7 +448,7 @@ static double localtimeOffset(DateTime *p){
   x.tz = 0;
   x.validJD = 0;
   computeJD(&x);
-  t = (x.rJD-2440587.5)*86400.0 + 0.5;
+  t = x.iJD/1000 - 2440587.5*86400.0;
 #ifdef HAVE_LOCALTIME_R
   {
     struct tm sLocal;
@@ -489,7 +490,7 @@ static double localtimeOffset(DateTime *p){
   y.validJD = 0;
   y.validTZ = 0;
   computeJD(&y);
-  return y.rJD - x.rJD;
+  return y.iJD - x.iJD;
 }
 #endif /* SQLITE_OMIT_LOCALTIME */
 
@@ -534,7 +535,7 @@ static int parseModifier(const char *zMod, DateTime *p){
       */
       if( strcmp(z, "localtime")==0 ){
         computeJD(p);
-        p->rJD += localtimeOffset(p);
+        p->iJD += localtimeOffset(p);
         clearYMD_HMS_TZ(p);
         rc = 0;
       }
@@ -545,20 +546,20 @@ static int parseModifier(const char *zMod, DateTime *p){
       /*
       **    unixepoch
       **
-      ** Treat the current value of p->rJD as the number of
+      ** Treat the current value of p->iJD 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->iJD = p->iJD/86400.0 + 2440587.5*86400000.0;
         clearYMD_HMS_TZ(p);
         rc = 0;
       }else if( strcmp(z, "utc")==0 ){
         double c1;
         computeJD(p);
         c1 = localtimeOffset(p);
-        p->rJD -= c1;
+        p->iJD -= c1;
         clearYMD_HMS_TZ(p);
-        p->rJD += c1 - localtimeOffset(p);
+        p->iJD += c1 - localtimeOffset(p);
         rc = 0;
       }
       break;
@@ -573,15 +574,14 @@ static int parseModifier(const char *zMod, DateTime *p){
       */
       if( strncmp(z, "weekday ", 8)==0 && getValue(&z[8],&r)>0
                  && (n=r)==r && n>=0 && r<7 ){
-        int Z;
+        sqlite3_int64 Z;
         computeYMD_HMS(p);
         p->validTZ = 0;
         p->validJD = 0;
         computeJD(p);
-        Z = p->rJD + 1.5;
-        Z %= 7;
+        Z = ((p->iJD + 129600000)/86400000) % 7;
         if( Z>n ) Z -= 7;
-        p->rJD += n - Z;
+        p->iJD += (n - Z)*86400000;
         clearYMD_HMS_TZ(p);
         rc = 0;
       }
@@ -637,18 +637,18 @@ static int parseModifier(const char *zMod, DateTime *p){
         */
         const char *z2 = z;
         DateTime tx;
-        int day;
+        sqlite3_int64 day;
         if( !isdigit(*(u8*)z2) ) z2++;
         memset(&tx, 0, sizeof(tx));
         if( parseHhMmSs(z2, &tx) ) break;
         computeJD(&tx);
-        tx.rJD -= 0.5;
-        day = (int)tx.rJD;
-        tx.rJD -= day;
-        if( z[0]=='-' ) tx.rJD = -tx.rJD;
+        tx.iJD -= 43200000;
+        day = tx.iJD/86400000;
+        tx.iJD -= day*86400000;
+        if( z[0]=='-' ) tx.iJD = -tx.iJD;
         computeJD(p);
         clearYMD_HMS_TZ(p);
-        p->rJD += tx.rJD;
+        p->iJD += tx.iJD;
         rc = 0;
         break;
       }
@@ -660,13 +660,13 @@ static int parseModifier(const char *zMod, DateTime *p){
       computeJD(p);
       rc = 0;
       if( n==3 && strcmp(z,"day")==0 ){
-        p->rJD += r;
+        p->iJD += r*86400000.0 + 0.5;
       }else if( n==4 && strcmp(z,"hour")==0 ){
-        p->rJD += r/24.0;
+        p->iJD += r*(86400000.0/24.0) + 0.5;
       }else if( n==6 && strcmp(z,"minute")==0 ){
-        p->rJD += r/(24.0*60.0);
+        p->iJD += r*(86400000.0/(24.0*60.0)) + 0.5;
       }else if( n==6 && strcmp(z,"second")==0 ){
-        p->rJD += r/(24.0*60.0*60.0);
+        p->iJD += r*(86400000.0/(24.0*60.0*60.0)) + 0.5;
       }else if( n==5 && strcmp(z,"month")==0 ){
         int x, y;
         computeYMD_HMS(p);
@@ -678,7 +678,7 @@ static int parseModifier(const char *zMod, DateTime *p){
         computeJD(p);
         y = r;
         if( y!=r ){
-          p->rJD += (r - y)*30.0;
+          p->iJD += (r - y)*30.0*86400000.0 + 0.5;
         }
       }else if( n==4 && strcmp(z,"year")==0 ){
         computeYMD_HMS(p);
@@ -715,12 +715,13 @@ static int isDate(
 ){
   int i;
   const unsigned char *z;
+  int eType;
   memset(p, 0, sizeof(*p));
   if( argc==0 ){
     setDateTimeToCurrent(context, p);
-  }else if( sqlite3_value_type(argv[0])==SQLITE_FLOAT ){
-    memset(p, 0, sizeof(*p));
-    p->rJD = sqlite3_value_double(argv[0]);
+  }else if( (eType = sqlite3_value_type(argv[0]))==SQLITE_FLOAT
+                   || eType==SQLITE_INTEGER ){
+    p->iJD = sqlite3_value_double(argv[0])*86400000.0 + 0.5;
     p->validJD = 1;
   }else{
     z = sqlite3_value_text(argv[0]);
@@ -755,7 +756,7 @@ static void juliandayFunc(
   DateTime x;
   if( isDate(context, argc, argv, &x)==0 ){
     computeJD(&x);
-    sqlite3_result_double(context, x.rJD);
+    sqlite3_result_double(context, x.iJD/86400000.0);
   }
 }
 
@@ -918,10 +919,10 @@ static void strftimeFunc(
           y.M = 1;
           y.D = 1;
           computeJD(&y);
-          nDay = x.rJD - y.rJD + 0.5;
+          nDay = (x.iJD - y.iJD)/86400000.0 + 0.5;
           if( zFmt[i]=='W' ){
             int wd;   /* 0=Monday, 1=Tuesday, ... 6=Sunday */
-            wd = ((int)(x.rJD+0.5)) % 7;
+            wd = ((x.iJD+43200000)/86400000) % 7;
             sqlite3_snprintf(3, &z[j],"%02d",(nDay+7-wd)/7);
             j += 2;
           }else{
@@ -931,7 +932,7 @@ static void strftimeFunc(
           break;
         }
         case 'J': {
-          sqlite3_snprintf(20, &z[j],"%.16g",x.rJD);
+          sqlite3_snprintf(20, &z[j],"%.16g",x.iJD/86400000.0);
           j+=strlen(&z[j]);
           break;
         }
@@ -939,12 +940,12 @@ static void strftimeFunc(
         case 'M':  sqlite3_snprintf(3, &z[j],"%02d",x.m); j+=2; break;
         case 's': {
           sqlite3_snprintf(30,&z[j],"%d",
-                           (int)((x.rJD-2440587.5)*86400.0 + 0.5));
+                           (int)(x.iJD/1000.0 - 210866760000.0));
           j += strlen(&z[j]);
           break;
         }
         case 'S':  sqlite3_snprintf(3,&z[j],"%02d",(int)x.s); j+=2; break;
-        case 'w':  z[j++] = (((int)(x.rJD+1.5)) % 7) + '0'; break;
+        case 'w':  z[j++] = (((x.iJD+129600000)/86400000) % 7) + '0'; break;
         case 'Y':  sqlite3_snprintf(5,&z[j],"%04d",x.Y); j+=strlen(&z[j]);break;
         default:   z[j++] = '%'; break;
       }
index 1d2129a87bd96e8941c0d56577c14cb508c326e9..35b142f524cc6fb7140a0d9d422be72ac0402377 100644 (file)
@@ -11,7 +11,7 @@
 # This file implements regression tests for SQLite library.  The
 # focus of this file is testing date and time functions.
 #
-# $Id: date.test,v 1.28 2008/06/12 05:16:15 shane Exp $
+# $Id: date.test,v 1.29 2008/06/12 16:35:39 drh Exp $
 
 set testdir [file dirname $argv0]
 source $testdir/tester.tcl
@@ -451,18 +451,36 @@ datetest 13.6 {strftime('%Y-%m-%d %H:%M:%S', '2007-01-01 23:59:59.6')} \
 datetest 13.7 {strftime('%Y-%m-%d %H:%M:%f', '2007-01-01 23:59:59.6')} \
   {2007-01-01 23:59:59.600}
 
-# Test for issues reported by BareFeet <list.sql@tandb.com.au> on mailing list
-# SELECT datetime(julianday('2008-06-12','utc'), 'localtime') should give 2008-06-12 00:00:00
-do_test date-13.8 {
-  execsql {
-    SELECT datetime(julianday('2008-06-12','utc'), 'localtime')
-  }
-} {{2008-06-12 00:00:00}}
-# SELECT date(julianday('2008-06-12', 'utc'), 'localtime') should give 2008-06-12
-do_test date-13.9 {
+# Test for issues reported by BareFeet (list.sql at tandb.com.au)
+# on mailing list on 2008-06-12.
+#
+# Put a floating point number in the database so that we can manipulate
+# raw bits using the hexio interface.
+#
+do_test date-14.1 {
   execsql {
-    SELECT date(julianday('2008-06-12','utc'), 'localtime')
+    PRAGMA auto_vacuum=OFF;
+    PRAGMA page_size = 1024;
+    CREATE TABLE t1(x);
+    INSERT INTO t1 VALUES(1.1);
   }
-} {2008-06-12}
-
+  db close
+  hexio_write test.db 2040 4142ba32bffffff9
+  sqlite3 db test.db
+  db eval {SELECT * FROM t1}
+} {2454629.5}
+
+# Changing the least significant byte of the floating point value between
+# 00 and FF should always generate a time of either 23:59:59 or 00:00:00,
+# never 24:00:00
+#
+for {set i 0} {$i<=255} {incr i} {
+  db close
+  hexio_write test.db 2047 [format %2x $i]
+  sqlite3 db test.db
+  do_test date-14.2.$i {
+    set date [db one {SELECT datetime(x) FROM t1}]
+    expr {$date eq "2008-06-12 00:00:00" || $date eq "2008-06-11 23:59:59"}
+  } {1}
+}
 finish_test