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 *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, 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                 }
00372                 while (*rp >= '0' && *rp <= '9');
00374                 qd->qd_sec = secs;
00375                 if (!qof_date_valid (qd))
00376                     return NULL;
00377             }
00378             break;
00379         case 'S':
00380             get_number (0, 61, 2);
00381             qd->qd_sec = val;
00382             break;
00383         case 'X':
00384         /* Fall through.  */
00385         case 'T':
00386             if (!recursive (HERE_T_FMT))
00387             {
00388                 *error = ERR_RECURSIVE_T;
00389                 PERR (" error=%s", QofDateErrorasString (*error));
00390                 return NULL;
00391             }
00392             break;
00393         case 'u':
00394             get_number (1, 7, 1);
00395             qd->qd_wday = val % 7;
00396             have_wday = 1;
00397             break;
00398         case 'g':
00399             get_number (0, 99, 2);
00400             /* XXX This cannot determine any field in TM.  */
00401             break;
00402         case 'G':
00403             if (*rp < '0' || *rp > '9')
00404             {
00405                 *error = ERR_G_INCOMPLETE;
00406                 PERR (" error=%s", QofDateErrorasString (*error));
00407                 return NULL;
00408             }
00409             /* XXX Ignore the number since we would need 
00410             some more information to compute a real date. */
00411             do
00412                 ++rp;
00413             while (*rp >= '0' && *rp <= '9');
00414             break;
00415         case 'U':
00416             get_number (0, 53, 2);
00417             week_no = val;
00418             have_uweek = 1;
00419             break;
00420         case 'W':
00421             get_number (0, 53, 2);
00422             week_no = val;
00423             have_wweek = 1;
00424             break;
00425         case 'V':
00426             get_number (0, 53, 2);
00427             /* XXX This cannot determine any field without some
00428             information. */
00429             break;
00430         case 'w':
00431             /* Match number of weekday.  */
00432             get_number (0, 6, 1);
00433             qd->qd_wday = val;
00434             have_wday = 1;
00435             break;
00436         case 'y':
00437             /* Match year within century.  */
00438             get_number (0, 99, 2);
00439             /* The "Year 2000: The Millennium Rollover" paper suggests that
00440             values in the range 69-99 refer to the twentieth century.  */
00441             qd->qd_year = val >= 69 ? val + 2000 : val + 1900;
00442             /* Indicate that we want to use the century, if specified.  */
00443             want_century = 1;
00444             want_xday = 1;
00445             break;
00446         case 'Y':
00447             /* Match year including century number.  */
00448             get_number (0, 999999999, 9);
00449             qd->qd_year = val;
00450             want_century = 0;
00451             want_xday = 1;
00452             break;
00453         case 'Z':
00454             /* XXX How to handle this?  */
00455             PINFO (" Z format - todo?");
00456             break;
00457         case 'z':
00458             /* We recognize two formats: if two digits are given, these
00459             specify hours. If fours digits are used, minutes are
00460             also specified. */
00461             {
00462                 gboolean neg;
00463                 gint n;
00464                 val = 0;
00465                 while (*rp == ' ')
00466                     ++rp;
00467                 if (*rp != '+' && *rp != '-')
00468                 {
00469                     *error = ERR_INVALID_Z;
00470                     PERR (" error=%s", QofDateErrorasString (*error));
00471                     return NULL;
00472                 }
00473                 neg = *rp++ == '-';
00474                 n = 0;
00475                 while (n < 4 && *rp >= '0' && *rp <= '9')
00476                 {
00477                     val = val * 10 + *rp++ - '0';
00478                     ++n;
00479                 }
00480                 if (n == 2)
00481                     val *= 100;
00482                 else if (n != 4)
00483                 {
00484                     /* Only two or four digits recognized. */
00485                     *error = ERR_YEAR_DIGITS;
00486                     PERR (" error=%s", QofDateErrorasString (*error));
00487                     return NULL;
00488                 }
00489                 else
00490                 {
00491                     /* We have to convert the minutes into decimal.  */
00492                     if (val % 100 >= 60)
00493                     {
00494                         *error = ERR_MIN_TO_DECIMAL;
00495                         PERR (" error=%s", QofDateErrorasString (*error));
00496                         return NULL;
00497                     }
00498                     val = (val / 100) * 100 + ((val % 100) * 50) / 30;
00499                 }
00500                 if (val > 1200)
00501                 {
00502                     *error = ERR_GMTOFF;
00503                     PERR (" error=%s", QofDateErrorasString (*error));
00504                     return NULL;
00505                 }
00506                 qd->qd_gmt_off = (val * 3600) / 100;
00507                 if (neg)
00508                     qd->qd_gmt_off = -qd->qd_gmt_off;
00509             }
00510             break;
00511         case 'E':
00512             /* We have no information about the era format. 
00513             Just use the normal format. */
00514             if (*fmt != 'c' && *fmt != 'C' && *fmt != 'y' && *fmt != 'Y'
00515                 && *fmt != 'x' && *fmt != 'X')
00516             {
00517                 /* This is an illegal format.  */
00518                 *error = ERR_INVALID_FORMAT;
00519                 PERR (" error=%s", QofDateErrorasString (*error));
00520                 return NULL;
00521             }
00522 
00523             goto start_over;
00524         case 'O':
00525             switch (*fmt++)
00526             {
00527             case 'd':
00528             case 'e':
00529                 /* Match day of month using alternate numeric symbols.  */
00530                 get_alt_number (1, 31, 2);
00531                 qd->qd_mday = val;
00532                 have_mday = 1;
00533                 want_xday = 1;
00534                 break;
00535             case 'H':
00536                 /* Match hour in 24-hour clock using alternate 
00537                 numeric symbols. */
00538                 get_alt_number (0, 23, 2);
00539                 qd->qd_hour = val;
00540                 have_I = 0;
00541                 break;
00542             case 'I':
00543                 /* Match hour in 12-hour clock using alternate 
00544                 numeric symbols. */
00545                 get_alt_number (1, 12, 2);
00546                 qd->qd_hour = val % 12;
00547                 have_I = 1;
00548                 break;
00549             case 'm':
00550                 /* Match month using alternate numeric symbols. */
00551                 get_alt_number (1, 12, 2);
00552                 qd->qd_mon = val - 1;
00553                 have_mon = 1;
00554                 want_xday = 1;
00555                 break;
00556             case 'M':
00557                 /* Match minutes using alternate numeric symbols.  */
00558                 get_alt_number (0, 59, 2);
00559                 qd->qd_min = val;
00560                 break;
00561             case 'S':
00562                 /* Match seconds using alternate numeric symbols.  */
00563                 get_alt_number (0, 61, 2);
00564                 qd->qd_sec = val;
00565                 break;
00566             case 'U':
00567                 get_alt_number (0, 53, 2);
00568                 week_no = val;
00569                 have_uweek = 1;
00570                 break;
00571             case 'W':
00572                 get_alt_number (0, 53, 2);
00573                 week_no = val;
00574                 have_wweek = 1;
00575                 break;
00576             case 'V':
00577                 get_alt_number (0, 53, 2);
00578                 /* XXX This cannot determine any field without
00579                 further information.  */
00580                 break;
00581             case 'w':
00582                 /* Match number of weekday using alternate numeric symbols. */
00583                 get_alt_number (0, 6, 1);
00584                 qd->qd_wday = val;
00585                 have_wday = 1;
00586                 break;
00587             case 'y':
00588                 /* Match year within century using alternate numeric symbols. */
00589                 get_alt_number (0, 99, 2);
00590                 qd->qd_year = val >= 69 ? val : val + 100;
00591                 want_xday = 1;
00592                 break;
00593             default:
00594                 {
00595                     *error = ERR_UNKNOWN_ERR;
00596                     PERR (" error=%s (first default)", 
00597                         QofDateErrorasString (*error));
00598                     return NULL;
00599                 }
00600             }
00601             break;
00602         default:
00603             {
00604                 *error = ERR_UNKNOWN_ERR;
00605                 PERR (" error=%s val=%s (second default)", 
00606                     QofDateErrorasString (*error), rp);
00607                 return NULL;
00608             }
00609         }
00610     }
00611 
00612     if (have_I && is_pm)
00613         qd->qd_hour += 12;
00614 
00615     if (century != -1)
00616     {
00617         if (want_century)
00619             qd->qd_year = qd->qd_year % 100 + (century - 19) * 100;
00620         else
00621             /* Only the century, but not the year.  */
00622             qd->qd_year = (century - 19) * 100;
00623     }
00624 
00625     if (era_cnt != -1)
00626     {
00627         if (era == NULL)
00628         {
00629             *error = ERR_INVALID_ERA;
00630             PERR (" error=%s", QofDateErrorasString (*error));
00631             return NULL;
00632         }
00633     }
00634     else if (want_era)
00635     {
00636     /* No era found but we have seen an E modifier.
00637     Rectify some values. */
00639         if (want_century && century == -1 && qd->qd_year < 69)
00640             qd->qd_year += 100;
00641     }
00642 
00643     if (want_xday && !have_wday)
00644     {
00645         if (!(have_mon && have_mday) && have_yday)
00646         {
00647             /* We don't have qd_mon and/or qd_mday, compute them.  */
00648             gint t_mon = 0;
00649             gint leap = qof_date_isleap (qd->qd_year);
00650             while (yeardays[leap][t_mon] <=
00651                 qd->qd_yday)
00652                 t_mon++;
00653             if (!have_mon)
00654                 qd->qd_mon = t_mon;
00655             if (!have_mday)
00656                 qd->qd_mday = qd->qd_yday - 
00657                     yeardays[leap][t_mon - 1] + 1;
00658         }
00659         set_day_of_the_week (qd);
00660     }
00661 
00662     if (want_xday && !have_yday)
00663         qd->qd_yday = qof_date_get_yday (qd->qd_mday,
00664             qd->qd_mon, qd->qd_year);
00665 
00666     if ((have_uweek || have_wweek) && have_wday)
00667     {
00668         gint save_wday = qd->qd_wday;
00669         gint save_mday = qd->qd_mday;
00670         gint save_mon = qd->qd_mon;
00671         gint w_offset = have_uweek ? 0 : 1;
00672 
00673         qd->qd_mday = 1;
00674         qd->qd_mon = 0;
00675         set_day_of_the_week (qd);
00676         if (have_mday)
00677             qd->qd_mday = save_mday;
00678         if (have_mon)
00679             qd->qd_mon = save_mon;
00680 
00681         if (!have_yday)
00682             qd->qd_yday = ((7 - (qd->qd_wday - w_offset)) % 7
00683                 + (week_no - 1) * 7 + save_wday - w_offset);
00684 
00685         if (!have_mday || !have_mon)
00686         {
00687             gint t_mon = 0;
00688 
00689             while (qof_date_get_yday (1, t_mon, qd->qd_year) <=
00690                 qd->qd_yday)
00691                 t_mon++;
00692             if (!have_mon)
00693                 qd->qd_mon = t_mon - 1;
00694             if (!have_mday)
00695                 qd->qd_mday = (qd->qd_yday -
00696                     qof_date_get_yday (1, t_mon, qd->qd_year));
00697         }
00698 
00699         qd->qd_wday = save_wday;
00700     }
00701 
00702     return (gchar *) rp;
00703 }

Generated on Thu Jan 31 22:50:26 2008 for QOF by  doxygen 1.5.4