Thread: patch for 60 seconds bug

patch for 60 seconds bug

From
Joseph Shraibman
Date:
This patch fixes the problem of doing:
select '2001-08-15 23:59:59.996'::timestamp;

... and getting a string with 60 in the seconds field.  This only fixes the method
EncodeDateTime(), the fix should be applied to EncodeTimeOnly() as well. (I didn't do it
because I thought that you might want to make this prettier before applying).

I was afraid to modify the fields in the passed in tm object.  Logically since it is only
output by this method even if internally it was changed it would have to go through this
method again to be output anyway, but I didn't want to mess with it.

This should be in the next 7.1.x release so when people dump from 7.1 to upgrade to 7.2
they won't have invalid data.


Interestingly:
  select '2001-08-15 23:59:59.995'::timestamp;
use to output:
  2001-08-15 23:59:59.99-04
on my system before I made this change.  Is this the result of some funkiness in my libc?
  I'm using a redhat 6.2ish system with glibc 2.1.3











*** datetime.c.orig    Wed Aug 15 21:00:05 2001
--- datetime.c    Wed Aug 15 22:05:56 2001
***************
*** 2080,2086 ****
   {
       int
    day,

    hour,
!
        min;
       double        sec;

       if ((tm->tm_mon < 1) || (tm->tm_mon > 12))
--- 2080,2091 ----
   {
       int
    day,

    hour,
!
        min,
!                 tmyear,
!                 tmmon,
!                 tmmday,
!                 tmhour,
!                 tmmin;
       double        sec;

       if ((tm->tm_mon < 1) || (tm->tm_mon > 12))
***************
*** 2088,2093 ****
--- 2093,2126 ----

       sec = (tm->tm_sec + fsec);

+     /* code to round the datetime so outputs with 60 seconds dont happen
+     I don't want to modify the tm values bec. I don't know if there would be side affects
+     so I created seperate variables.
+     I left BC dates alone.
+     */
+     tmyear = tm->tm_year;
+     tmmon =  tm->tm_mon;
+     tmmday = tm->tm_mday;
+     tmhour = tm->tm_hour;
+     tmmin = tm->tm_min;
+     if (sec >= 59.995){
+         sec = 0;
+         if (++tmmin > 59){
+
    tmmin = 0;
+
    if (++tmhour > 23){
+
        tmhour = 0;
+
        if (++tmmday > day_tab[isleap(tmyear)][tmmon - 1]){
+
            tmmday = 1;
+
            if (++tmmon > 12){
+
                tmmon = 1;
+
                tmyear++;/* what about BC? */
+
            }
+
        }
+
    }
+         }
+     };
+
+
       switch (style)
       {
               /* compatible with ISO date formats */
***************
*** 2096,2102 ****
               if (tm->tm_year > 0)
               {

    sprintf(str, "%04d-%02d-%02d %02d:%02d:",
!
                tm->tm_year, tm->tm_mon, tm->tm_mday, tm->tm_hour, tm->tm_min);

    sprintf((str + strlen(str)), ((fsec != 0) ? "%05.2f" : "%02.0f"), sec);


    if ((*tzn != NULL) && (tm->tm_isdst >= 0))
--- 2129,2135 ----
               if (tm->tm_year > 0)
               {

    sprintf(str, "%04d-%02d-%02d %02d:%02d:",
!
                tmyear, tmmon, tmmday, tmhour, tmmin);

    sprintf((str + strlen(str)), ((fsec != 0) ? "%05.2f" : "%02.0f"), sec);


    if ((*tzn != NULL) && (tm->tm_isdst >= 0))
***************
*** 2129,2158 ****
               /* compatible with Oracle/Ingres date formats */
           case USE_SQL_DATES:
               if (EuroDates)
!
        sprintf(str, "%02d/%02d", tm->tm_mday, tm->tm_mon);
               else
!
        sprintf(str, "%02d/%02d", tm->tm_mon, tm->tm_mday);

               if (tm->tm_year > 0)
               {

    sprintf((str + 5), "/%04d %02d:%02d:%05.2f",
!
                tm->tm_year, tm->tm_hour, tm->tm_min, sec);


    if ((*tzn != NULL) && (tm->tm_isdst >= 0))

        sprintf((str + strlen(str)), " %.*s", MAXTZLEN, *tzn);
               }
               else

    sprintf((str + 5), "/%04d %02d:%02d %s",
!
              -(tm->tm_year - 1), tm->tm_hour, tm->tm_min, "BC");
               break;

               /* German variant on European style */
           case USE_GERMAN_DATES:
!
    sprintf(str, "%02d.%02d", tm->tm_mday, tm->tm_mon);
               if (tm->tm_year > 0)
               {

    sprintf((str + 5), ".%04d %02d:%02d:%05.2f",
!
                tm->tm_year, tm->tm_hour, tm->tm_min, sec);


    if ((*tzn != NULL) && (tm->tm_isdst >= 0))

        sprintf((str + strlen(str)), " %.*s", MAXTZLEN, *tzn);
--- 2162,2191 ----
               /* compatible with Oracle/Ingres date formats */
           case USE_SQL_DATES:
               if (EuroDates)
!
        sprintf(str, "%02d/%02d", tmmday, tmmon);
               else
!
        sprintf(str, "%02d/%02d", tmmon, tmmday);

               if (tm->tm_year > 0)
               {

    sprintf((str + 5), "/%04d %02d:%02d:%05.2f",
!
                tmyear, tmhour, tmmin, sec);


    if ((*tzn != NULL) && (tm->tm_isdst >= 0))

        sprintf((str + strlen(str)), " %.*s", MAXTZLEN, *tzn);
               }
               else

    sprintf((str + 5), "/%04d %02d:%02d %s",
!
              -(tm->tm_year - 1), tmhour, tmmin, "BC");
               break;

               /* German variant on European style */
           case USE_GERMAN_DATES:
!
    sprintf(str, "%02d.%02d", tmmday, tmmon);
               if (tm->tm_year > 0)
               {

    sprintf((str + 5), ".%04d %02d:%02d:%05.2f",
!
                tmyear, tmhour, tmmin, sec);


    if ((*tzn != NULL) && (tm->tm_isdst >= 0))

        sprintf((str + strlen(str)), " %.*s", MAXTZLEN, *tzn);
***************
*** 2165,2193 ****
               /* backward-compatible with traditional Postgres abstime dates */
           case USE_POSTGRES_DATES:
           default:
!
    day = date2j(tm->tm_year, tm->tm_mon, tm->tm_mday);
               tm->tm_wday = j2day(day);

               strncpy(str, days[tm->tm_wday], 3);
               strcpy((str + 3), " ");

               if (EuroDates)
!
        sprintf((str + 4), "%02d %3s", tm->tm_mday, months[tm->tm_mon - 1]);
               else
!
        sprintf((str + 4), "%3s %02d", months[tm->tm_mon - 1], tm->tm_mday);

               if (tm->tm_year > 0)
               {
!
        sprintf((str + 10), " %02d:%02d", tm->tm_hour, tm->tm_min);

    if (fsec != 0)

    {
!
            sprintf((str + 16), ":%05.2f %04d", sec, tm->tm_year);

        if ((*tzn != NULL) && (tm->tm_isdst >= 0))

            sprintf((str + strlen(str)), " %.*s", MAXTZLEN, *tzn);

    }

    else

    {
!
            sprintf((str + 16), ":%02.0f %04d", sec, tm->tm_year);

        if ((*tzn != NULL) && (tm->tm_isdst >= 0))

            sprintf((str + strlen(str)), " %.*s", MAXTZLEN, *tzn);

    }
--- 2198,2226 ----
               /* backward-compatible with traditional Postgres abstime dates */
           case USE_POSTGRES_DATES:
           default:
!
    day = date2j(tmyear, tmmon, tmmday);
               tm->tm_wday = j2day(day);

               strncpy(str, days[tm->tm_wday], 3);
               strcpy((str + 3), " ");

               if (EuroDates)
!
        sprintf((str + 4), "%02d %3s", tmmday, months[tmmon - 1]);
               else
!
        sprintf((str + 4), "%3s %02d", months[tmmon - 1], tmmday);

               if (tm->tm_year > 0)
               {
!
        sprintf((str + 10), " %02d:%02d", tmhour, tmmin);

    if (fsec != 0)

    {
!
            sprintf((str + 16), ":%05.2f %04d", sec, tmyear);

        if ((*tzn != NULL) && (tm->tm_isdst >= 0))

            sprintf((str + strlen(str)), " %.*s", MAXTZLEN, *tzn);

    }

    else

    {
!
            sprintf((str + 16), ":%02.0f %04d", sec, tmyear);

        if ((*tzn != NULL) && (tm->tm_isdst >= 0))

            sprintf((str + strlen(str)), " %.*s", MAXTZLEN, *tzn);

    }
--
Joseph Shraibman
jks@selectacast.net
Increase signal to noise ratio.  http://www.targabot.com


Re: patch for 60 seconds bug

From
Tom Lane
Date:
Joseph Shraibman <jks@selectacast.net> writes:
> This patch fixes the problem of doing:
> select '2001-08-15 23:59:59.996'::timestamp;

This seems the hardest possible way to do it.  What I suggested awhile
back was to round the timestamp value to two fractional digits before
disassembling it into y/m/d/h/m/s, which'd take only something like

    foo = rint(foo * 100.0) / 100.0;

at the right place.  Thomas didn't like that, for reasons that I do not
recall offhand, and nothing's been done since.

            regards, tom lane

Re: patch for 60 seconds bug

From
Thomas Lockhart
Date:
> ...  Thomas didn't like that, for reasons that I do not
> recall offhand, and nothing's been done since.

I would like to look at a more general rounding solution for the next
release. There are some SQL9x compatibility issues involved wrt defining
the number of digits.

                     - Thomas