QOF 0.7.5
|
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 }