Header And Logo

PostgreSQL
| The world's most advanced open source database.

datetime.c

Go to the documentation of this file.
00001 /* src/interfaces/ecpg/pgtypeslib/datetime.c */
00002 
00003 #include "postgres_fe.h"
00004 
00005 #include <time.h>
00006 #include <ctype.h>
00007 #include <float.h>
00008 #include <limits.h>
00009 
00010 #include "extern.h"
00011 #include "dt.h"
00012 #include "pgtypes_error.h"
00013 #include "pgtypes_date.h"
00014 
00015 date *
00016 PGTYPESdate_new(void)
00017 {
00018     date       *result;
00019 
00020     result = (date *) pgtypes_alloc(sizeof(date));
00021     /* result can be NULL if we run out of memory */
00022     return result;
00023 }
00024 
00025 void
00026 PGTYPESdate_free(date * d)
00027 {
00028     free(d);
00029 }
00030 
00031 date
00032 PGTYPESdate_from_timestamp(timestamp dt)
00033 {
00034     date        dDate;
00035 
00036     dDate = 0;                  /* suppress compiler warning */
00037 
00038     if (!TIMESTAMP_NOT_FINITE(dt))
00039     {
00040 #ifdef HAVE_INT64_TIMESTAMP
00041         /* Microseconds to days */
00042         dDate = (dt / USECS_PER_DAY);
00043 #else
00044         /* Seconds to days */
00045         dDate = (dt / (double) SECS_PER_DAY);
00046 #endif
00047     }
00048 
00049     return dDate;
00050 }
00051 
00052 date
00053 PGTYPESdate_from_asc(char *str, char **endptr)
00054 {
00055 
00056     date        dDate;
00057     fsec_t      fsec;
00058     struct tm   tt,
00059                *tm = &tt;
00060     int         dtype;
00061     int         nf;
00062     char       *field[MAXDATEFIELDS];
00063     int         ftype[MAXDATEFIELDS];
00064     char        lowstr[MAXDATELEN + 1];
00065     char       *realptr;
00066     char      **ptr = (endptr != NULL) ? endptr : &realptr;
00067 
00068     bool        EuroDates = FALSE;
00069 
00070     errno = 0;
00071     if (strlen(str) >= sizeof(lowstr))
00072     {
00073         errno = PGTYPES_DATE_BAD_DATE;
00074         return INT_MIN;
00075     }
00076 
00077     if (ParseDateTime(str, lowstr, field, ftype, &nf, ptr) != 0 ||
00078         DecodeDateTime(field, ftype, nf, &dtype, tm, &fsec, EuroDates) != 0)
00079     {
00080         errno = PGTYPES_DATE_BAD_DATE;
00081         return INT_MIN;
00082     }
00083 
00084     switch (dtype)
00085     {
00086         case DTK_DATE:
00087             break;
00088 
00089         case DTK_EPOCH:
00090             if (GetEpochTime(tm) < 0)
00091             {
00092                 errno = PGTYPES_DATE_BAD_DATE;
00093                 return INT_MIN;
00094             }
00095             break;
00096 
00097         default:
00098             errno = PGTYPES_DATE_BAD_DATE;
00099             return INT_MIN;
00100     }
00101 
00102     dDate = (date2j(tm->tm_year, tm->tm_mon, tm->tm_mday) - date2j(2000, 1, 1));
00103 
00104     return dDate;
00105 }
00106 
00107 char *
00108 PGTYPESdate_to_asc(date dDate)
00109 {
00110     struct tm   tt,
00111                *tm = &tt;
00112     char        buf[MAXDATELEN + 1];
00113     int         DateStyle = 1;
00114     bool        EuroDates = FALSE;
00115 
00116     j2date(dDate + date2j(2000, 1, 1), &(tm->tm_year), &(tm->tm_mon), &(tm->tm_mday));
00117     EncodeDateOnly(tm, DateStyle, buf, EuroDates);
00118     return pgtypes_strdup(buf);
00119 }
00120 
00121 void
00122 PGTYPESdate_julmdy(date jd, int *mdy)
00123 {
00124     int         y,
00125                 m,
00126                 d;
00127 
00128     j2date((int) (jd + date2j(2000, 1, 1)), &y, &m, &d);
00129     mdy[0] = m;
00130     mdy[1] = d;
00131     mdy[2] = y;
00132 }
00133 
00134 void
00135 PGTYPESdate_mdyjul(int *mdy, date * jdate)
00136 {
00137     /* month is mdy[0] */
00138     /* day   is mdy[1] */
00139     /* year  is mdy[2] */
00140 
00141     *jdate = (date) (date2j(mdy[2], mdy[0], mdy[1]) - date2j(2000, 1, 1));
00142 }
00143 
00144 int
00145 PGTYPESdate_dayofweek(date dDate)
00146 {
00147     /*
00148      * Sunday:  0 Monday:      1 Tuesday:     2 Wednesday:   3 Thursday: 4
00149      * Friday:      5 Saturday:    6
00150      */
00151     return (int) (dDate + date2j(2000, 1, 1) + 1) % 7;
00152 }
00153 
00154 void
00155 PGTYPESdate_today(date * d)
00156 {
00157     struct tm   ts;
00158 
00159     GetCurrentDateTime(&ts);
00160     if (errno == 0)
00161         *d = date2j(ts.tm_year, ts.tm_mon, ts.tm_mday) - date2j(2000, 1, 1);
00162     return;
00163 }
00164 
00165 #define PGTYPES_DATE_NUM_MAX_DIGITS     20      /* should suffice for most
00166                                                  * years... */
00167 
00168 #define PGTYPES_FMTDATE_DAY_DIGITS_LZ       1   /* LZ means "leading zeroes" */
00169 #define PGTYPES_FMTDATE_DOW_LITERAL_SHORT   2
00170 #define PGTYPES_FMTDATE_MONTH_DIGITS_LZ     3
00171 #define PGTYPES_FMTDATE_MONTH_LITERAL_SHORT 4
00172 #define PGTYPES_FMTDATE_YEAR_DIGITS_SHORT   5
00173 #define PGTYPES_FMTDATE_YEAR_DIGITS_LONG    6
00174 
00175 int
00176 PGTYPESdate_fmt_asc(date dDate, const char *fmtstring, char *outbuf)
00177 {
00178     static struct
00179     {
00180         char       *format;
00181         int         component;
00182     }           mapping[] =
00183     {
00184         /*
00185          * format items have to be sorted according to their length, since the
00186          * first pattern that matches gets replaced by its value
00187          */
00188         {
00189             "ddd", PGTYPES_FMTDATE_DOW_LITERAL_SHORT
00190         },
00191         {
00192             "dd", PGTYPES_FMTDATE_DAY_DIGITS_LZ
00193         },
00194         {
00195             "mmm", PGTYPES_FMTDATE_MONTH_LITERAL_SHORT
00196         },
00197         {
00198             "mm", PGTYPES_FMTDATE_MONTH_DIGITS_LZ
00199         },
00200         {
00201             "yyyy", PGTYPES_FMTDATE_YEAR_DIGITS_LONG
00202         },
00203         {
00204             "yy", PGTYPES_FMTDATE_YEAR_DIGITS_SHORT
00205         },
00206         {
00207             NULL, 0
00208         }
00209     };
00210 
00211     union un_fmt_comb replace_val;
00212     int         replace_type;
00213 
00214     int         i;
00215     int         dow;
00216     char       *start_pattern;
00217     struct tm   tm;
00218 
00219     /* copy the string over */
00220     strcpy(outbuf, fmtstring);
00221 
00222     /* get the date */
00223     j2date(dDate + date2j(2000, 1, 1), &(tm.tm_year), &(tm.tm_mon), &(tm.tm_mday));
00224     dow = PGTYPESdate_dayofweek(dDate);
00225 
00226     for (i = 0; mapping[i].format != NULL; i++)
00227     {
00228         while ((start_pattern = strstr(outbuf, mapping[i].format)) != NULL)
00229         {
00230             switch (mapping[i].component)
00231             {
00232                 case PGTYPES_FMTDATE_DOW_LITERAL_SHORT:
00233                     replace_val.str_val = pgtypes_date_weekdays_short[dow];
00234                     replace_type = PGTYPES_TYPE_STRING_CONSTANT;
00235                     break;
00236                 case PGTYPES_FMTDATE_DAY_DIGITS_LZ:
00237                     replace_val.uint_val = tm.tm_mday;
00238                     replace_type = PGTYPES_TYPE_UINT_2_LZ;
00239                     break;
00240                 case PGTYPES_FMTDATE_MONTH_LITERAL_SHORT:
00241                     replace_val.str_val = months[tm.tm_mon - 1];
00242                     replace_type = PGTYPES_TYPE_STRING_CONSTANT;
00243                     break;
00244                 case PGTYPES_FMTDATE_MONTH_DIGITS_LZ:
00245                     replace_val.uint_val = tm.tm_mon;
00246                     replace_type = PGTYPES_TYPE_UINT_2_LZ;
00247                     break;
00248                 case PGTYPES_FMTDATE_YEAR_DIGITS_LONG:
00249                     replace_val.uint_val = tm.tm_year;
00250                     replace_type = PGTYPES_TYPE_UINT_4_LZ;
00251                     break;
00252                 case PGTYPES_FMTDATE_YEAR_DIGITS_SHORT:
00253                     replace_val.uint_val = tm.tm_year % 100;
00254                     replace_type = PGTYPES_TYPE_UINT_2_LZ;
00255                     break;
00256                 default:
00257 
00258                     /*
00259                      * should not happen, set something anyway
00260                      */
00261                     replace_val.str_val = " ";
00262                     replace_type = PGTYPES_TYPE_STRING_CONSTANT;
00263             }
00264             switch (replace_type)
00265             {
00266                 case PGTYPES_TYPE_STRING_MALLOCED:
00267                 case PGTYPES_TYPE_STRING_CONSTANT:
00268                     strncpy(start_pattern, replace_val.str_val,
00269                             strlen(replace_val.str_val));
00270                     if (replace_type == PGTYPES_TYPE_STRING_MALLOCED)
00271                         free(replace_val.str_val);
00272                     break;
00273                 case PGTYPES_TYPE_UINT:
00274                     {
00275                         char       *t = pgtypes_alloc(PGTYPES_DATE_NUM_MAX_DIGITS);
00276 
00277                         if (!t)
00278                             return -1;
00279                         snprintf(t, PGTYPES_DATE_NUM_MAX_DIGITS,
00280                                  "%u", replace_val.uint_val);
00281                         strncpy(start_pattern, t, strlen(t));
00282                         free(t);
00283                     }
00284                     break;
00285                 case PGTYPES_TYPE_UINT_2_LZ:
00286                     {
00287                         char       *t = pgtypes_alloc(PGTYPES_DATE_NUM_MAX_DIGITS);
00288 
00289                         if (!t)
00290                             return -1;
00291                         snprintf(t, PGTYPES_DATE_NUM_MAX_DIGITS,
00292                                  "%02u", replace_val.uint_val);
00293                         strncpy(start_pattern, t, strlen(t));
00294                         free(t);
00295                     }
00296                     break;
00297                 case PGTYPES_TYPE_UINT_4_LZ:
00298                     {
00299                         char       *t = pgtypes_alloc(PGTYPES_DATE_NUM_MAX_DIGITS);
00300 
00301                         if (!t)
00302                             return -1;
00303                         snprintf(t, PGTYPES_DATE_NUM_MAX_DIGITS,
00304                                  "%04u", replace_val.uint_val);
00305                         strncpy(start_pattern, t, strlen(t));
00306                         free(t);
00307                     }
00308                     break;
00309                 default:
00310 
00311                     /*
00312                      * doesn't happen (we set replace_type to
00313                      * PGTYPES_TYPE_STRING_CONSTANT in case of an error above)
00314                      */
00315                     break;
00316             }
00317         }
00318     }
00319     return 0;
00320 }
00321 
00322 
00323 /*
00324  * PGTYPESdate_defmt_asc
00325  *
00326  * function works as follows:
00327  *   - first we analyze the paramters
00328  *   - if this is a special case with no delimiters, add delimters
00329  *   - find the tokens. First we look for numerical values. If we have found
00330  *     less than 3 tokens, we check for the months' names and thereafter for
00331  *     the abbreviations of the months' names.
00332  *   - then we see which parameter should be the date, the month and the
00333  *     year and from these values we calculate the date
00334  */
00335 
00336 #define PGTYPES_DATE_MONTH_MAXLENGTH        20  /* probably even less  :-) */
00337 int
00338 PGTYPESdate_defmt_asc(date * d, const char *fmt, char *str)
00339 {
00340     /*
00341      * token[2] = { 4,6 } means that token 2 starts at position 4 and ends at
00342      * (including) position 6
00343      */
00344     int         token[3][2];
00345     int         token_values[3] = {-1, -1, -1};
00346     char       *fmt_token_order;
00347     char       *fmt_ystart,
00348                *fmt_mstart,
00349                *fmt_dstart;
00350     unsigned int i;
00351     int         reading_digit;
00352     int         token_count;
00353     char       *str_copy;
00354     struct tm   tm;
00355 
00356     tm.tm_year = tm.tm_mon = tm.tm_mday = 0;    /* keep compiler quiet */
00357 
00358     if (!d || !str || !fmt)
00359     {
00360         errno = PGTYPES_DATE_ERR_EARGS;
00361         return -1;
00362     }
00363 
00364     /* analyze the fmt string */
00365     fmt_ystart = strstr(fmt, "yy");
00366     fmt_mstart = strstr(fmt, "mm");
00367     fmt_dstart = strstr(fmt, "dd");
00368 
00369     if (!fmt_ystart || !fmt_mstart || !fmt_dstart)
00370     {
00371         errno = PGTYPES_DATE_ERR_EARGS;
00372         return -1;
00373     }
00374 
00375     if (fmt_ystart < fmt_mstart)
00376     {
00377         /* y m */
00378         if (fmt_dstart < fmt_ystart)
00379         {
00380             /* d y m */
00381             fmt_token_order = "dym";
00382         }
00383         else if (fmt_dstart > fmt_mstart)
00384         {
00385             /* y m d */
00386             fmt_token_order = "ymd";
00387         }
00388         else
00389         {
00390             /* y d m */
00391             fmt_token_order = "ydm";
00392         }
00393     }
00394     else
00395     {
00396         /* fmt_ystart > fmt_mstart */
00397         /* m y */
00398         if (fmt_dstart < fmt_mstart)
00399         {
00400             /* d m y */
00401             fmt_token_order = "dmy";
00402         }
00403         else if (fmt_dstart > fmt_ystart)
00404         {
00405             /* m y d */
00406             fmt_token_order = "myd";
00407         }
00408         else
00409         {
00410             /* m d y */
00411             fmt_token_order = "mdy";
00412         }
00413     }
00414 
00415     /*
00416      * handle the special cases where there is no delimiter between the
00417      * digits. If we see this:
00418      *
00419      * only digits, 6 or 8 bytes then it might be ddmmyy and ddmmyyyy (or
00420      * similar)
00421      *
00422      * we reduce it to a string with delimiters and continue processing
00423      */
00424 
00425     /* check if we have only digits */
00426     reading_digit = 1;
00427     for (i = 0; str[i]; i++)
00428     {
00429         if (!isdigit((unsigned char) str[i]))
00430         {
00431             reading_digit = 0;
00432             break;
00433         }
00434     }
00435     if (reading_digit)
00436     {
00437         int         frag_length[3];
00438         int         target_pos;
00439 
00440         i = strlen(str);
00441         if (i != 8 && i != 6)
00442         {
00443             errno = PGTYPES_DATE_ERR_ENOSHORTDATE;
00444             return -1;
00445         }
00446         /* okay, this really is the special case */
00447 
00448         /*
00449          * as long as the string, one additional byte for the terminator and 2
00450          * for the delimiters between the 3 fiedls
00451          */
00452         str_copy = pgtypes_alloc(strlen(str) + 1 + 2);
00453         if (!str_copy)
00454             return -1;
00455 
00456         /* determine length of the fragments */
00457         if (i == 6)
00458         {
00459             frag_length[0] = 2;
00460             frag_length[1] = 2;
00461             frag_length[2] = 2;
00462         }
00463         else
00464         {
00465             if (fmt_token_order[0] == 'y')
00466             {
00467                 frag_length[0] = 4;
00468                 frag_length[1] = 2;
00469                 frag_length[2] = 2;
00470             }
00471             else if (fmt_token_order[1] == 'y')
00472             {
00473                 frag_length[0] = 2;
00474                 frag_length[1] = 4;
00475                 frag_length[2] = 2;
00476             }
00477             else
00478             {
00479                 frag_length[0] = 2;
00480                 frag_length[1] = 2;
00481                 frag_length[2] = 4;
00482             }
00483         }
00484         target_pos = 0;
00485 
00486         /*
00487          * XXX: Here we could calculate the positions of the tokens and save
00488          * the for loop down there where we again check with isdigit() for
00489          * digits.
00490          */
00491         for (i = 0; i < 3; i++)
00492         {
00493             int         start_pos = 0;
00494 
00495             if (i >= 1)
00496                 start_pos += frag_length[0];
00497             if (i == 2)
00498                 start_pos += frag_length[1];
00499 
00500             strncpy(str_copy + target_pos, str + start_pos,
00501                     frag_length[i]);
00502             target_pos += frag_length[i];
00503             if (i != 2)
00504             {
00505                 str_copy[target_pos] = ' ';
00506                 target_pos++;
00507             }
00508         }
00509         str_copy[target_pos] = '\0';
00510     }
00511     else
00512     {
00513         str_copy = pgtypes_strdup(str);
00514         if (!str_copy)
00515             return -1;
00516 
00517         /* convert the whole string to lower case */
00518         for (i = 0; str_copy[i]; i++)
00519             str_copy[i] = (char) pg_tolower((unsigned char) str_copy[i]);
00520     }
00521 
00522     /* look for numerical tokens */
00523     reading_digit = 0;
00524     token_count = 0;
00525     for (i = 0; i < strlen(str_copy); i++)
00526     {
00527         if (!isdigit((unsigned char) str_copy[i]) && reading_digit)
00528         {
00529             /* the token is finished */
00530             token[token_count][1] = i - 1;
00531             reading_digit = 0;
00532             token_count++;
00533         }
00534         else if (isdigit((unsigned char) str_copy[i]) && !reading_digit)
00535         {
00536             /* we have found a token */
00537             token[token_count][0] = i;
00538             reading_digit = 1;
00539         }
00540     }
00541 
00542     /*
00543      * we're at the end of the input string, but maybe we are still reading a
00544      * number...
00545      */
00546     if (reading_digit)
00547     {
00548         token[token_count][1] = i - 1;
00549         token_count++;
00550     }
00551 
00552 
00553     if (token_count < 2)
00554     {
00555         /*
00556          * not all tokens found, no way to find 2 missing tokens with string
00557          * matches
00558          */
00559         free(str_copy);
00560         errno = PGTYPES_DATE_ERR_ENOSHORTDATE;
00561         return -1;
00562     }
00563 
00564     if (token_count != 3)
00565     {
00566         /*
00567          * not all tokens found but we may find another one with string
00568          * matches by testing for the months names and months abbreviations
00569          */
00570         char       *month_lower_tmp = pgtypes_alloc(PGTYPES_DATE_MONTH_MAXLENGTH);
00571         char       *start_pos;
00572         int         j;
00573         int         offset;
00574         int         found = 0;
00575         char      **list;
00576 
00577         if (!month_lower_tmp)
00578         {
00579             /* free variables we alloc'ed before */
00580             free(str_copy);
00581             return -1;
00582         }
00583         list = pgtypes_date_months;
00584         for (i = 0; list[i]; i++)
00585         {
00586             for (j = 0; j < PGTYPES_DATE_MONTH_MAXLENGTH; j++)
00587             {
00588                 month_lower_tmp[j] = (char) pg_tolower((unsigned char) list[i][j]);
00589                 if (!month_lower_tmp[j])
00590                 {
00591                     /* properly terminated */
00592                     break;
00593                 }
00594             }
00595             if ((start_pos = strstr(str_copy, month_lower_tmp)))
00596             {
00597                 offset = start_pos - str_copy;
00598 
00599                 /*
00600                  * sort the new token into the numeric tokens, shift them if
00601                  * necessary
00602                  */
00603                 if (offset < token[0][0])
00604                 {
00605                     token[2][0] = token[1][0];
00606                     token[2][1] = token[1][1];
00607                     token[1][0] = token[0][0];
00608                     token[1][1] = token[0][1];
00609                     token_count = 0;
00610                 }
00611                 else if (offset < token[1][0])
00612                 {
00613                     token[2][0] = token[1][0];
00614                     token[2][1] = token[1][1];
00615                     token_count = 1;
00616                 }
00617                 else
00618                     token_count = 2;
00619                 token[token_count][0] = offset;
00620                 token[token_count][1] = offset + strlen(month_lower_tmp) - 1;
00621 
00622                 /*
00623                  * the value is the index of the month in the array of months
00624                  * + 1 (January is month 0)
00625                  */
00626                 token_values[token_count] = i + 1;
00627                 found = 1;
00628                 break;
00629             }
00630 
00631             /*
00632              * evil[tm] hack: if we read the pgtypes_date_months and haven't
00633              * found a match, reset list to point to pgtypes_date_months_short
00634              * and reset the counter variable i
00635              */
00636             if (list == pgtypes_date_months)
00637             {
00638                 if (list[i + 1] == NULL)
00639                 {
00640                     list = months;
00641                     i = -1;
00642                 }
00643             }
00644         }
00645         if (!found)
00646         {
00647             free(month_lower_tmp);
00648             free(str_copy);
00649             errno = PGTYPES_DATE_ERR_ENOTDMY;
00650             return -1;
00651         }
00652 
00653         /*
00654          * here we found a month. token[token_count] and
00655          * token_values[token_count] reflect the month's details.
00656          *
00657          * only the month can be specified with a literal. Here we can do a
00658          * quick check if the month is at the right position according to the
00659          * format string because we can check if the token that we expect to
00660          * be the month is at the position of the only token that already has
00661          * a value. If we wouldn't check here we could say "December 4 1990"
00662          * with a fmt string of "dd mm yy" for 12 April 1990.
00663          */
00664         if (fmt_token_order[token_count] != 'm')
00665         {
00666             /* deal with the error later on */
00667             token_values[token_count] = -1;
00668         }
00669         free(month_lower_tmp);
00670     }
00671 
00672     /* terminate the tokens with ASCII-0 and get their values */
00673     for (i = 0; i < 3; i++)
00674     {
00675         *(str_copy + token[i][1] + 1) = '\0';
00676         /* A month already has a value set, check for token_value == -1 */
00677         if (token_values[i] == -1)
00678         {
00679             errno = 0;
00680             token_values[i] = strtol(str_copy + token[i][0], (char **) NULL, 10);
00681             /* strtol sets errno in case of an error */
00682             if (errno)
00683                 token_values[i] = -1;
00684         }
00685         if (fmt_token_order[i] == 'd')
00686             tm.tm_mday = token_values[i];
00687         else if (fmt_token_order[i] == 'm')
00688             tm.tm_mon = token_values[i];
00689         else if (fmt_token_order[i] == 'y')
00690             tm.tm_year = token_values[i];
00691     }
00692     free(str_copy);
00693 
00694     if (tm.tm_mday < 1 || tm.tm_mday > 31)
00695     {
00696         errno = PGTYPES_DATE_BAD_DAY;
00697         return -1;
00698     }
00699 
00700     if (tm.tm_mon < 1 || tm.tm_mon > MONTHS_PER_YEAR)
00701     {
00702         errno = PGTYPES_DATE_BAD_MONTH;
00703         return -1;
00704     }
00705 
00706     if (tm.tm_mday == 31 && (tm.tm_mon == 4 || tm.tm_mon == 6 || tm.tm_mon == 9 || tm.tm_mon == 11))
00707     {
00708         errno = PGTYPES_DATE_BAD_DAY;
00709         return -1;
00710     }
00711 
00712     if (tm.tm_mon == 2 && tm.tm_mday > 29)
00713     {
00714         errno = PGTYPES_DATE_BAD_DAY;
00715         return -1;
00716     }
00717 
00718     *d = date2j(tm.tm_year, tm.tm_mon, tm.tm_mday) - date2j(2000, 1, 1);
00719 
00720     return 0;
00721 }