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