QOF 0.8.4
qofstrptime.c
00001 /***************************************************************************
00002  *            qofstrptime.c
00003  *
00004  *  Wed May 31 09:34:13 2006
00005  *  Copyright (C) 2002, 2004, 2005, 2006 
00006  *  Free Software Foundation, Inc.
00007  *  This file is modified from the GNU C Library.
00008  ****************************************************************************/
00009 /*
00010  * This program is free software; you can redistribute it and/or modify
00011  * it under the terms of the GNU General Public License as published by
00012  * the Free Software Foundation; either version 2 of the License, or
00013  * (at your option) any later version.
00014  * 
00015  * This program is distributed in the hope that it will be useful,
00016  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00017  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00018  * GNU General Public License for more details.
00019  * 
00020  * You should have received a copy of the GNU General Public License
00021  * along with this program; if not, write to the Free Software
00022  * Foundation, Inc., 51 Franklin Street, Fifth Floor Boston, MA 02110-1301,  USA
00023  */
00024 /* 
00025 Modified version of strptime from GNU C Library.
00026 
00027 1. Removed preprocessor directives that are always true or always 
00028    false within QOF
00029 2. Extended variables to full 64bit ranges, even on 32bit platforms.
00030 3. Replaced time_t with QofTime to prevent overflow in 2038.
00031 4. Replaced struct tm with QofDate to prevent overflow.
00032 5. Implement an error handler to provide more information.
00033 Neil Williams <linux@codehelp.co.uk>
00034 */
00035 
00036 #include "config.h"
00037 #include <ctype.h>
00038 #include <string.h>
00039 #include <glib.h>
00040 #include "qof.h"
00041 #include "qofdate-p.h"
00042 
00043 static QofLogModule log_module = QOF_MOD_DATE;
00044 
00045 AS_STRING_FUNC (QofDateError , ENUM_ERR_LIST)
00046 
00047 #define match_char(ch1, ch2) if (ch1 != ch2) return NULL
00048 # define match_string(cs1, s2) \
00049   (strncasecmp ((cs1), (s2), strlen (cs1)) ? 0 : ((s2) += strlen (cs1), 1))
00050 /* We intentionally do not use isdigit() for testing because this will
00051    lead to problems with the wide character version.  */
00052 #define get_number(from, to, n)                         \
00053     do {                                                \
00054     gint __n = n;                                       \
00055     val = 0;                                            \
00056     while (*rp == ' ')                                  \
00057         ++rp;                                           \
00058     if (*rp < '0' || *rp > '9')                         \
00059     {                                                   \
00060         *error = ERR_OUT_OF_RANGE;                      \
00061         PERR (" error=%s", QofDateErrorasString (*error)); \
00062         return NULL;                                    \
00063     }                                                   \
00064     do {                                                \
00065         val *= 10;                                      \
00066         val += *rp++ - '0';                             \
00067     }                                                   \
00068     while (--__n > 0 && val * 10 <= to && *rp >= '0' && *rp <= '9');    \
00069     if (val < from || val > to)                         \
00070     {                                                   \
00071         *error = ERR_INVALID_DELIMITER;                 \
00072         PERR (" error=%s", QofDateErrorasString (*error)); \
00073         return NULL;                                    \
00074     }                                                   \
00075     } while (0)
00076 
00077 /* If we don't have the alternate representation.  */
00078 # define get_alt_number(from, to, n) \
00079     get_number(from, to, n)
00080 
00081 #define recursive(new_fmt) \
00082   (*(new_fmt) != '\0' && (rp = strptime_internal (rp, (new_fmt), qd, error)) != NULL)
00083 
00084 static gchar const weekday_name[][10] = {
00085     "Sunday", "Monday", "Tuesday", "Wednesday",
00086     "Thursday", "Friday", "Saturday"
00087 };
00088 static gchar const ab_weekday_name[][4] = {
00089     "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
00090 };
00091 static gchar const month_name[][10] = {
00092     "January", "February", "March", "April", "May", "June",
00093     "July", "August", "September", "October", "November", "December"
00094 };
00095 static gchar const ab_month_name[][4] = {
00096     "Jan", "Feb", "Mar", "Apr", "May", "Jun",
00097     "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
00098 };
00099 
00100 # define HERE_D_T_FMT "%a %b %e %H:%M:%S %Y"
00101 # define HERE_D_FMT "%m/%d/%y"
00102 # define HERE_AM_STR "AM"
00103 # define HERE_PM_STR "PM"
00104 # define HERE_T_FMT_AMPM "%I:%M:%S %p"
00105 # define HERE_T_FMT "%H:%M:%S"
00106 #define raw 1;
00107 
00108 /* retained for a few areas where qd_mon and qd_mday are unknown.
00109 */
00110 static const gushort yeardays[2][13] = {
00111     {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365},
00112     {0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366}
00113 };
00114 
00115 /* Compute the day of the week.  */
00116 void
00117 set_day_of_the_week (QofDate * qd)
00118 {
00119     gint64 days;
00120     /* We know that January 1st 1970 was a Thursday (= 4). */
00121     days = days_between (1970, qd->qd_year);
00122     /* qd_wday is always positive. */
00123     if (days < 0)
00124         days *= -1;
00125     days--;
00126     days += qof_date_get_yday (qd->qd_mday, 
00127         qd->qd_mon, qd->qd_year) + 4;
00128     qd->qd_wday = ((days % 7) + 7) % 7;
00129 }
00130 
00131 gchar *
00132 strptime_internal (const gchar * rp, const gchar * fmt, 
00133     QofDate * qd, QofDateError * error)
00134 {
00135     const gchar * G_GNUC_UNUSED rp_backup;
00136     gint64 val, century, want_century;
00137     gint want_era, have_wday, want_xday, have_yday;
00138     gint have_mon, have_mday, have_uweek, have_wweek;
00139     gint week_no, have_I, is_pm, cnt, G_GNUC_UNUSED decided, era_cnt;
00140     struct era_entry *era;
00141 
00142     have_I = is_pm = 0;
00143     century = -1;
00144     decided = raw;
00145     era_cnt = -1;
00146     want_century = 0;
00147     want_era = 0;
00148     era = NULL;
00149     week_no = 0;
00150     *error = ERR_NO_ERROR;
00151 
00152     have_wday = want_xday = have_yday = have_mon = 0;
00153     have_mday = have_uweek = have_wweek = 0;
00154 
00155     while (*fmt != '\0')
00156     {
00157         /* A white space in the format string matches 0 more 
00158         or white space in the input string.  */
00159         if (isspace (*fmt))
00160         {
00161             while (isspace (*rp))
00162                 ++rp;
00163             ++fmt;
00164             continue;
00165         }
00166 
00167         /* Any character but `%' must be matched by the 
00168         same character in the iput string. */
00169         if (*fmt != '%')
00170         {
00171             match_char (*fmt++, *rp++);
00172             continue;
00173         }
00174 
00175         ++fmt;
00176         /* We need this for handling the `E' modifier.  */
00177       start_over:
00178 
00179         /* Make back up of current processing pointer.  */
00180         //rp_backup = rp;
00181 
00182         switch (*fmt++)
00183         {
00184         case '%':
00185             /* Match the `%' character itself.  */
00186             match_char ('%', *rp++);
00187             break;
00188         case 'a':
00189         case 'A':
00190             /* Match day of week.  */
00191             for (cnt = 0; cnt < 7; ++cnt)
00192             {
00193               if (match_string (weekday_name[cnt], rp)
00194               || match_string (ab_weekday_name[cnt], rp))
00195               break;
00196             }
00197             if (cnt == 7)
00198             {
00199                 /* Does not match a weekday name.  */
00200                 *error = ERR_WEEKDAY_NAME;
00201                 PERR (" error=%s", QofDateErrorasString (*error));
00202                 return NULL;
00203             }
00204             qd->qd_wday = cnt;
00205             have_wday = 1;
00206             break;
00207         case 'b':
00208         case 'B':
00209         case 'h':
00210             /* Match month name.  */
00211             for (cnt = 0; cnt < 12; ++cnt)
00212             {
00213                 if (match_string (month_name[cnt], rp)
00214                     || match_string (ab_month_name[cnt], rp))
00215                 {
00216                     decided = raw;
00217                     break;
00218                 }
00219             }
00220             if (cnt == 12)
00221             {
00222                 /* Does not match a month name.  */
00223                 *error = ERR_MONTH_NAME;
00224                 PERR (" error=%s", QofDateErrorasString (*error));
00225                 return NULL;
00226             }
00227             qd->qd_mon = cnt;
00228             want_xday = 1;
00229             break;
00230         case 'c':
00231             /* Match locale's date and time format.  */
00232             if (!recursive (HERE_D_T_FMT))
00233             {
00234                 *error = ERR_LOCALE_DATE_TIME;
00235                 PERR (" error=%s", QofDateErrorasString (*error));
00236                 return NULL;
00237             }
00238             want_xday = 1;
00239             break;
00240         case 'C':
00241             /* Match century number.  */
00242             get_number (0, 99, 2);
00243             century = val;
00244             want_xday = 1;
00245             break;
00246         case 'd':
00247         case 'e':
00248             /* Match day of month.  */
00249             get_number (1, 31, 2);
00250             qd->qd_mday = val;
00251             have_mday = 1;
00252             want_xday = 1;
00253             break;
00254         case 'F':
00255             if (!recursive ("%Y-%m-%d"))
00256                 return NULL;
00257             want_xday = 1;
00258             break;
00259         case 'x':
00260             /* Fall through.  */
00261         case 'D':
00262             /* Match standard day format.  */
00263             if (!recursive (HERE_D_FMT))
00264             {
00265                 *error = ERR_STANDARD_DAY;
00266                 PERR (" error=%s", QofDateErrorasString (*error));
00267                 return NULL;
00268             }
00269             want_xday = 1;
00270             break;
00271         case 'k':
00272         case 'H':
00273             /* Match hour in 24-hour clock. */
00274             get_number (0, 23, 2);
00275             qd->qd_hour = val;
00276             have_I = 0;
00277             break;
00278         case 'l':
00279             /* Match hour in 12-hour clock. GNU extension. */
00280         case 'I':
00281             /* Match hour in 12-hour clock. */
00282             get_number (1, 12, 2);
00283             qd->qd_hour = val % 12;
00284             have_I = 1;
00285             break;
00286         case 'j':
00287             /* Match day number of year.  */
00288             get_number (1, 366, 3);
00289             qd->qd_yday = val - 1;
00290             have_yday = 1;
00291             break;
00292         case 'm':
00293             /* Match number of month.  */
00294             get_number (1, 12, 2);
00295             qd->qd_mon = val;
00296             have_mon = 1;
00297             want_xday = 1;
00298             break;
00299         case 'M':
00300             /* Match minute.  */
00301             get_number (0, 59, 2);
00302             qd->qd_min = val;
00303             break;
00304         case 'N':
00305         {
00306             /* match nanoseconds */
00307             gint n;
00308             n = val = 0;
00309             while (n < 9 && *rp >= '0' && *rp <= '9')
00310             {
00311                 val = val * 10 + *rp++ - '0';
00312                 ++n;
00313             }
00314             qd->qd_nanosecs = val;
00315             break;
00316         }
00317         case 'n':
00318         case 't':
00319             /* Match any white space.  */
00320             while (isspace (*rp))
00321                 ++rp;
00322             break;
00323         case 'p':
00324             /* Match locale's equivalent of AM/PM.  */
00325             if (!match_string (HERE_AM_STR, rp))
00326             {
00327                 if (match_string (HERE_PM_STR, rp))
00328                     is_pm = 1;
00329                 else
00330                 {
00331                     *error = ERR_LOCALE_AMPM;
00332                     PERR (" error=%s", QofDateErrorasString (*error));
00333                     return NULL;
00334                 }
00335             }
00336             break;
00337         case 'r':
00338             if (!recursive (HERE_T_FMT_AMPM))
00339             {
00340                 *error = ERR_TIME_AMPM;
00341                 PERR (" error=%s", QofDateErrorasString (*error));
00342                 return NULL;
00343             }
00344             break;
00345         case 'R':
00346             if (!recursive ("%H:%M"))
00347             {
00348                 *error = ERR_RECURSIVE_R;
00349                 PERR (" error=%s", QofDateErrorasString (*error));
00350                 return NULL;
00351             }
00352             break;
00353         case 's':
00354             {
00355                 /* The number of seconds may be very high so we 
00356                 cannot use the `get_number' macro.  Instead read 
00357                 the number character for character and construct 
00358                 the result while doing this. */
00359                 QofTimeSecs secs = 0;
00360                 if (*rp < '0' || *rp > '9')
00361                     /* We need at least one digit.  */
00362                 {
00363                     *error = ERR_SECS_NO_DIGITS;
00364                     PERR (" error=%s", QofDateErrorasString (*error));
00365                     return NULL;
00366                 }
00367                 do
00368                 {
00369                     secs *= 10;
00370                     secs += *rp++ - '0';
00371                 } while (*rp >= '0' && *rp <= '9');
00372                 qd->qd_sec = secs;
00373                 /* 's' is an epoch format */
00374                 qd->qd_year = 1970;
00375             }
00376             break;
00377         case 'S':
00378             get_number (0, 61, 2);
00379             qd->qd_sec = val;
00380             break;
00381         case 'X':
00382         /* Fall through.  */
00383         case 'T':
00384             if (!recursive (HERE_T_FMT))
00385             {
00386                 *error = ERR_RECURSIVE_T;
00387                 PERR (" error=%s", QofDateErrorasString (*error));
00388                 return NULL;
00389             }
00390             break;
00391         case 'u':
00392             get_number (1, 7, 1);
00393             qd->qd_wday = val % 7;
00394             have_wday = 1;
00395             break;
00396         case 'g':
00397             get_number (0, 99, 2);
00398             /* XXX This cannot determine any field in TM.  */
00399             break;
00400         case 'G':
00401             if (*rp < '0' || *rp > '9')
00402             {
00403                 *error = ERR_G_INCOMPLETE;
00404                 PERR (" error=%s", QofDateErrorasString (*error));
00405                 return NULL;
00406             }
00407             /* XXX Ignore the number since we would need 
00408             some more information to compute a real date. */
00409             do
00410                 ++rp;
00411             while (*rp >= '0' && *rp <= '9');
00412             break;
00413         case 'U':
00414             get_number (0, 53, 2);
00415             week_no = val;
00416             have_uweek = 1;
00417             break;
00418         case 'W':
00419             get_number (0, 53, 2);
00420             week_no = val;
00421             have_wweek = 1;
00422             break;
00423         case 'V':
00424             get_number (0, 53, 2);
00425             /* XXX This cannot determine any field without some
00426             information. */
00427             break;
00428         case 'w':
00429             /* Match number of weekday.  */
00430             get_number (0, 6, 1);
00431             qd->qd_wday = val;
00432             have_wday = 1;
00433             break;
00434         case 'y':
00435             /* Match year within century.  */
00436             get_number (0, 99, 2);
00437             /* The "Year 2000: The Millennium Rollover" paper suggests that
00438             values in the range 69-99 refer to the twentieth century.  */
00439             qd->qd_year = val >= 69 ? val + 2000 : val + 1900;
00440             /* Indicate that we want to use the century, if specified.  */
00441             want_century = 1;
00442             want_xday = 1;
00443             break;
00444         case 'Y':
00445             /* Match year including century number.  */
00446             get_number (0, 999999999, 9);
00447             qd->qd_year = val;
00448             want_century = 0;
00449             want_xday = 1;
00450             break;
00451         case 'Z':
00452             /* XXX How to handle this?  */
00453             PINFO (" Z format - todo?");
00454             break;
00455         case 'z':
00456             /* We recognize two formats: if two digits are given, these
00457             specify hours. If fours digits are used, minutes are
00458             also specified. */
00459             {
00460                 gboolean neg;
00461                 gint n;
00462                 val = 0;
00463                 while (*rp == ' ')
00464                     ++rp;
00465                 if (*rp != '+' && *rp != '-')
00466                 {
00467                     *error = ERR_INVALID_Z;
00468                     PERR (" error=%s", QofDateErrorasString (*error));
00469                     return NULL;
00470                 }
00471                 neg = *rp++ == '-';
00472                 n = 0;
00473                 while (n < 4 && *rp >= '0' && *rp <= '9')
00474                 {
00475                     val = val * 10 + *rp++ - '0';
00476                     ++n;
00477                 }
00478                 if (n == 2)
00479                     val *= 100;
00480                 else if (n != 4)
00481                 {
00482                     /* Only two or four digits recognized. */
00483                     *error = ERR_YEAR_DIGITS;
00484                     PERR (" error=%s", QofDateErrorasString (*error));
00485                     return NULL;
00486                 }
00487                 else
00488                 {
00489                     /* We have to convert the minutes into decimal.  */
00490                     if (val % 100 >= 60)
00491                     {
00492                         *error = ERR_MIN_TO_DECIMAL;
00493                         PERR (" error=%s", QofDateErrorasString (*error));
00494                         return NULL;
00495                     }
00496                     val = (val / 100) * 100 + ((val % 100) * 50) / 30;
00497                 }
00498                 if (val > 1200)
00499                 {
00500                     *error = ERR_GMTOFF;
00501                     PERR (" error=%s", QofDateErrorasString (*error));
00502                     return NULL;
00503                 }
00504                 qd->qd_gmt_off = (val * 3600) / 100;
00505                 if (neg)
00506                     qd->qd_gmt_off = -qd->qd_gmt_off;
00507             }
00508             break;
00509         case 'E':
00510             /* We have no information about the era format. 
00511             Just use the normal format. */
00512             if (*fmt != 'c' && *fmt != 'C' && *fmt != 'y' && *fmt != 'Y'
00513                 && *fmt != 'x' && *fmt != 'X')
00514             {
00515                 /* This is an illegal format.  */
00516                 *error = ERR_INVALID_FORMAT;
00517                 PERR (" error=%s", QofDateErrorasString (*error));
00518                 return NULL;
00519             }
00520 
00521             goto start_over;
00522         case 'O':
00523             switch (*fmt++)
00524             {
00525             case 'd':
00526             case 'e':
00527                 /* Match day of month using alternate numeric symbols.  */
00528                 get_alt_number (1, 31, 2);
00529                 qd->qd_mday = val;
00530                 have_mday = 1;
00531                 want_xday = 1;
00532                 break;
00533             case 'H':
00534                 /* Match hour in 24-hour clock using alternate 
00535                 numeric symbols. */
00536                 get_alt_number (0, 23, 2);
00537                 qd->qd_hour = val;
00538                 have_I = 0;
00539                 break;
00540             case 'I':
00541                 /* Match hour in 12-hour clock using alternate 
00542                 numeric symbols. */
00543                 get_alt_number (1, 12, 2);
00544                 qd->qd_hour = val % 12;
00545                 have_I = 1;
00546                 break;
00547             case 'm':
00548                 /* Match month using alternate numeric symbols. */
00549                 get_alt_number (1, 12, 2);
00550                 qd->qd_mon = val - 1;
00551                 have_mon = 1;
00552                 want_xday = 1;
00553                 break;
00554             case 'M':
00555                 /* Match minutes using alternate numeric symbols.  */
00556                 get_alt_number (0, 59, 2);
00557                 qd->qd_min = val;
00558                 break;
00559             case 'S':
00560                 /* Match seconds using alternate numeric symbols.  */
00561                 get_alt_number (0, 61, 2);
00562                 qd->qd_sec = val;
00563                 break;
00564             case 'U':
00565                 get_alt_number (0, 53, 2);
00566                 week_no = val;
00567                 have_uweek = 1;
00568                 break;
00569             case 'W':
00570                 get_alt_number (0, 53, 2);
00571                 week_no = val;
00572                 have_wweek = 1;
00573                 break;
00574             case 'V':
00575                 get_alt_number (0, 53, 2);
00576                 /* XXX This cannot determine any field without
00577                 further information.  */
00578                 break;
00579             case 'w':
00580                 /* Match number of weekday using alternate numeric symbols. */
00581                 get_alt_number (0, 6, 1);
00582                 qd->qd_wday = val;
00583                 have_wday = 1;
00584                 break;
00585             case 'y':
00586                 /* Match year within century using alternate numeric symbols. */
00587                 get_alt_number (0, 99, 2);
00588                 qd->qd_year = val >= 69 ? val : val + 100;
00589                 want_xday = 1;
00590                 break;
00591             default:
00592                 {
00593                     *error = ERR_UNKNOWN_ERR;
00594                     PERR (" error=%s (first default)", 
00595                         QofDateErrorasString (*error));
00596                     return NULL;
00597                 }
00598             }
00599             break;
00600         default:
00601             {
00602                 *error = ERR_UNKNOWN_ERR;
00603                 PERR (" error=%s val=%s (second default)", 
00604                     QofDateErrorasString (*error), rp);
00605                 return NULL;
00606             }
00607         }
00608     }
00609 
00610     if (have_I && is_pm)
00611         qd->qd_hour += 12;
00612 
00613     if (century != -1)
00614     {
00615         if (want_century)
00617             qd->qd_year = qd->qd_year % 100 + (century - 19) * 100;
00618         else
00619             /* Only the century, but not the year.  */
00620             qd->qd_year = (century - 19) * 100;
00621     }
00622 
00623     if (era_cnt != -1)
00624     {
00625         if (era == NULL)
00626         {
00627             *error = ERR_INVALID_ERA;
00628             PERR (" error=%s", QofDateErrorasString (*error));
00629             return NULL;
00630         }
00631     }
00632     else if (want_era)
00633     {
00634     /* No era found but we have seen an E modifier.
00635     Rectify some values. */
00637         if (want_century && century == -1 && qd->qd_year < 69)
00638             qd->qd_year += 100;
00639     }
00640 
00641     if (want_xday && !have_wday)
00642     {
00643         if (!(have_mon && have_mday) && have_yday)
00644         {
00645             /* We don't have qd_mon and/or qd_mday, compute them.  */
00646             gint t_mon = 0;
00647             gint leap = qof_date_isleap (qd->qd_year);
00648             while (yeardays[leap][t_mon] <=
00649                 qd->qd_yday)
00650                 t_mon++;
00651             if (!have_mon)
00652                 qd->qd_mon = t_mon;
00653             if (!have_mday)
00654                 qd->qd_mday = qd->qd_yday - 
00655                     yeardays[leap][t_mon - 1] + 1;
00656         }
00657         set_day_of_the_week (qd);
00658     }
00659 
00660     if (want_xday && !have_yday)
00661         qd->qd_yday = qof_date_get_yday (qd->qd_mday,
00662             qd->qd_mon, qd->qd_year);
00663 
00664     if ((have_uweek || have_wweek) && have_wday)
00665     {
00666         gint save_wday = qd->qd_wday;
00667         gint save_mday = qd->qd_mday;
00668         gint save_mon = qd->qd_mon;
00669         gint w_offset = have_uweek ? 0 : 1;
00670 
00671         qd->qd_mday = 1;
00672         qd->qd_mon = 0;
00673         set_day_of_the_week (qd);
00674         if (have_mday)
00675             qd->qd_mday = save_mday;
00676         if (have_mon)
00677             qd->qd_mon = save_mon;
00678 
00679         if (!have_yday)
00680             qd->qd_yday = ((7 - (qd->qd_wday - w_offset)) % 7
00681                 + (week_no - 1) * 7 + save_wday - w_offset);
00682 
00683         if (!have_mday || !have_mon)
00684         {
00685             gint t_mon = 0;
00686 
00687             while (qof_date_get_yday (1, t_mon, qd->qd_year) <=
00688                 qd->qd_yday)
00689                 t_mon++;
00690             if (!have_mon)
00691                 qd->qd_mon = t_mon - 1;
00692             if (!have_mday)
00693                 qd->qd_mday = (qd->qd_yday -
00694                     qof_date_get_yday (1, t_mon, qd->qd_year));
00695         }
00696 
00697         qd->qd_wday = save_wday;
00698     }
00699 
00700     return (gchar *) rp;
00701 }