00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033
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
00051
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
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
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
00116 void
00117 set_day_of_the_week (QofDate * qd)
00118 {
00119 gint64 days;
00120
00121 days = days_between (1970, qd->qd_year);
00122
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
00158
00159 if (isspace (*fmt))
00160 {
00161 while (isspace (*rp))
00162 ++rp;
00163 ++fmt;
00164 continue;
00165 }
00166
00167
00168
00169 if (*fmt != '%')
00170 {
00171 match_char (*fmt++, *rp++);
00172 continue;
00173 }
00174
00175 ++fmt;
00176
00177 start_over:
00178
00179
00180 rp_backup = rp;
00181
00182 switch (*fmt++)
00183 {
00184 case '%':
00185
00186 match_char ('%', *rp++);
00187 break;
00188 case 'a':
00189 case 'A':
00190
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
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
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
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
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
00242 get_number (0, 99, 2);
00243 century = val;
00244 want_xday = 1;
00245 break;
00246 case 'd':
00247 case 'e':
00248
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
00261 case 'D':
00262
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
00274 get_number (0, 23, 2);
00275 qd->qd_hour = val;
00276 have_I = 0;
00277 break;
00278 case 'l':
00279
00280 case 'I':
00281
00282 get_number (1, 12, 2);
00283 qd->qd_hour = val % 12;
00284 have_I = 1;
00285 break;
00286 case 'j':
00287
00288 get_number (1, 366, 3);
00289 qd->qd_yday = val - 1;
00290 have_yday = 1;
00291 break;
00292 case 'm':
00293
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
00301 get_number (0, 59, 2);
00302 qd->qd_min = val;
00303 break;
00304 case 'N':
00305 {
00306
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
00320 while (isspace (*rp))
00321 ++rp;
00322 break;
00323 case 'p':
00324
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
00356
00357
00358
00359 QofTimeSecs secs = 0;
00360 if (*rp < '0' || *rp > '9')
00361
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
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
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
00410
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
00428
00429 break;
00430 case 'w':
00431
00432 get_number (0, 6, 1);
00433 qd->qd_wday = val;
00434 have_wday = 1;
00435 break;
00436 case 'y':
00437
00438 get_number (0, 99, 2);
00439
00440
00441 qd->qd_year = val >= 69 ? val + 2000 : val + 1900;
00442
00443 want_century = 1;
00444 want_xday = 1;
00445 break;
00446 case 'Y':
00447
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
00455 PINFO (" Z format - todo?");
00456 break;
00457 case 'z':
00458
00459
00460
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
00485 *error = ERR_YEAR_DIGITS;
00486 PERR (" error=%s", QofDateErrorasString (*error));
00487 return NULL;
00488 }
00489 else
00490 {
00491
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
00513
00514 if (*fmt != 'c' && *fmt != 'C' && *fmt != 'y' && *fmt != 'Y'
00515 && *fmt != 'x' && *fmt != 'X')
00516 {
00517
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
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
00537
00538 get_alt_number (0, 23, 2);
00539 qd->qd_hour = val;
00540 have_I = 0;
00541 break;
00542 case 'I':
00543
00544
00545 get_alt_number (1, 12, 2);
00546 qd->qd_hour = val % 12;
00547 have_I = 1;
00548 break;
00549 case 'm':
00550
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
00558 get_alt_number (0, 59, 2);
00559 qd->qd_min = val;
00560 break;
00561 case 'S':
00562
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
00579
00580 break;
00581 case 'w':
00582
00583 get_alt_number (0, 6, 1);
00584 qd->qd_wday = val;
00585 have_wday = 1;
00586 break;
00587 case 'y':
00588
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
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
00637
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
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 }