QOF: qofstrptime.c Source File

QOF 0.7.5

- Main Page
- Related Pages
- Modules
- Data Structures
- Files

- File List
- Globals

qofstrptime.c
1 /***************************************************************************
2 * qofstrptime.c
3 *
4 * Wed May 31 09:34:13 2006
5 * Copyright (C) 2002, 2004, 2005, 2006
6 * Free Software Foundation, Inc.
7 * This file is modified from the GNU C Library.
8 ****************************************************************************/
9 /*
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, write to the Free Software
22 * Foundation, Inc., 51 Franklin Street, Fifth Floor Boston, MA 02110-1301, USA
23 */
24 /*
25 Modified version of strptime from GNU C Library.
26
27 1. Removed preprocessor directives that are always true or always
28 false within QOF
29 2. Extended variables to full 64bit ranges, even on 32bit platforms.
30 3. Replaced time_t with QofTime to prevent overflow in 2038.
31 4. Replaced struct tm with QofDate to prevent overflow.
32 5. Implement an error handler to provide more information.
33 Neil Williams <linux@codehelp.co.uk>
34 */
35
36 #include "config.h"
37 #include <ctype.h>
38 #include <string.h>
39 #include <glib.h>
40 #include "qof.h"
41 #include "qofdate-p.h"
42
43 static QofLogModule log_module = QOF_MOD_DATE;
44
45 AS_STRING_FUNC (QofDateError , ENUM_ERR_LIST)
46
47 #define match_char(ch1, ch2) if (ch1 != ch2) return NULL
48 # define match_string(cs1, s2) \
49 (strncasecmp ((cs1), (s2), strlen (cs1)) ? 0 : ((s2) += strlen (cs1), 1))
50 /* We intentionally do not use isdigit() for testing because this will
51 lead to problems with the wide character version. */
52 #define get_number(from, to, n) \
53 do { \
54 gint __n = n; \
55 val = 0; \
56 while (*rp == ' ') \
57 ++rp; \
58 if (*rp < '0' || *rp > '9') \
59 { \
60 *error = ERR_OUT_OF_RANGE; \
61 PERR (" error=%s", QofDateErrorasString (*error)); \
62 return NULL; \
63 } \
64 do { \
65 val *= 10; \
66 val += *rp++ - '0'; \
67 } \
68 while (--__n > 0 && val * 10 <= to && *rp >= '0' && *rp <= '9'); \
69 if (val < from || val > to) \
70 { \
71 *error = ERR_INVALID_DELIMITER; \
72 PERR (" error=%s", QofDateErrorasString (*error)); \
73 return NULL; \
74 } \
75 } while (0)
76
77 /* If we don't have the alternate representation. */
78 # define get_alt_number(from, to, n) \
79 get_number(from, to, n)
80
81 #define recursive(new_fmt) \
82 (*(new_fmt) != '\0' && (rp = strptime_internal (rp, (new_fmt), qd, error)) != NULL)
83
84 static gchar const weekday_name[][10] = {
85 "Sunday", "Monday", "Tuesday", "Wednesday",
86 "Thursday", "Friday", "Saturday"
87 };
88 static gchar const ab_weekday_name[][4] = {
89 "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
90 };
91 static gchar const month_name[][10] = {
92 "January", "February", "March", "April", "May", "June",
93 "July", "August", "September", "October", "November", "December"
94 };
95 static gchar const ab_month_name[][4] = {
96 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
97 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
98 };
99
100 # define HERE_D_T_FMT "%a %b %e %H:%M:%S %Y"
101 # define HERE_D_FMT "%m/%d/%y"
102 # define HERE_AM_STR "AM"
103 # define HERE_PM_STR "PM"
104 # define HERE_T_FMT_AMPM "%I:%M:%S %p"
105 # define HERE_T_FMT "%H:%M:%S"
106 #define raw 1;
107
108 /* retained for a few areas where qd_mon and qd_mday are unknown.
109 */
110 static const gushort yeardays[2][13] = {
111 {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365},
112 {0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366}
113 };
114
115 /* Compute the day of the week. */
116 void
117 set_day_of_the_week (QofDate * qd)
118 {
119 gint64 days;
120 /* We know that January 1st 1970 was a Thursday (= 4). */
121 days = days_between (1970, qd->qd_year);
122 /* qd_wday is always positive. */
123 if (days < 0)
124 days *= -1;
125 days--;
126 days += qof_date_get_yday (qd->qd_mday,
127 qd->qd_mon, qd->qd_year) + 4;
128 qd->qd_wday = ((days % 7) + 7) % 7;
129 }
130
131 gchar *
132 strptime_internal (const gchar * rp, const gchar * fmt,
133 QofDate * qd, QofDateError * error)
134 {
135 const gchar *rp_backup;
136 gint64 val, century, want_century;
137 gint want_era, have_wday, want_xday, have_yday;
138 gint have_mon, have_mday, have_uweek, have_wweek;
139 gint week_no, have_I, is_pm, cnt, decided, era_cnt;
140 struct era_entry *era;
141
142 have_I = is_pm = 0;
143 century = -1;
144 decided = raw;
145 era_cnt = -1;
146 want_century = 0;
147 want_era = 0;
148 era = NULL;
149 week_no = 0;
150 *error = ERR_NO_ERROR;
151
152 have_wday = want_xday = have_yday = have_mon = 0;
153 have_mday = have_uweek = have_wweek = 0;
154
155 while (*fmt != '\0')
156 {
157 /* A white space in the format string matches 0 more
158 or white space in the input string. */
159 if (isspace (*fmt))
160 {
161 while (isspace (*rp))
162 ++rp;
163 ++fmt;
164 continue;
165 }
166
167 /* Any character but `%' must be matched by the
168 same character in the iput string. */
169 if (*fmt != '%')
170 {
171 match_char (*fmt++, *rp++);
172 continue;
173 }
174
175 ++fmt;
176 /* We need this for handling the `E' modifier. */
177 start_over:
178
179 /* Make back up of current processing pointer. */
180 rp_backup = rp;
181
182 switch (*fmt++)
183 {
184 case '%':
185 /* Match the `%' character itself. */
186 match_char ('%', *rp++);
187 break;
188 case 'a':
189 case 'A':
190 /* Match day of week. */
191 for (cnt = 0; cnt < 7; ++cnt)
192 {
193 if (match_string (weekday_name[cnt], rp)
194 || match_string (ab_weekday_name[cnt], rp))
195 break;
196 }
197 if (cnt == 7)
198 {
199 /* Does not match a weekday name. */
200 *error = ERR_WEEKDAY_NAME;
201 PERR (" error=%s", QofDateErrorasString (*error));
202 return NULL;
203 }
204 qd->qd_wday = cnt;
205 have_wday = 1;
206 break;
207 case 'b':
208 case 'B':
209 case 'h':
210 /* Match month name. */
211 for (cnt = 0; cnt < 12; ++cnt)
212 {
213 if (match_string (month_name[cnt], rp)
214 || match_string (ab_month_name[cnt], rp))
215 {
216 decided = raw;
217 break;
218 }
219 }
220 if (cnt == 12)
221 {
222 /* Does not match a month name. */
223 *error = ERR_MONTH_NAME;
224 PERR (" error=%s", QofDateErrorasString (*error));
225 return NULL;
226 }
227 qd->qd_mon = cnt;
228 want_xday = 1;
229 break;
230 case 'c':
231 /* Match locale's date and time format. */
232 if (!recursive (HERE_D_T_FMT))
233 {
234 *error = ERR_LOCALE_DATE_TIME;
235 PERR (" error=%s", QofDateErrorasString (*error));
236 return NULL;
237 }
238 want_xday = 1;
239 break;
240 case 'C':
241 /* Match century number. */
242 get_number (0, 99, 2);
243 century = val;
244 want_xday = 1;
245 break;
246 case 'd':
247 case 'e':
248 /* Match day of month. */
249 get_number (1, 31, 2);
250 qd->qd_mday = val;
251 have_mday = 1;
252 want_xday = 1;
253 break;
254 case 'F':
255 if (!recursive ("%Y-%m-%d"))
256 return NULL;
257 want_xday = 1;
258 break;
259 case 'x':
260 /* Fall through. */
261 case 'D':
262 /* Match standard day format. */
263 if (!recursive (HERE_D_FMT))
264 {
265 *error = ERR_STANDARD_DAY;
266 PERR (" error=%s", QofDateErrorasString (*error));
267 return NULL;
268 }
269 want_xday = 1;
270 break;
271 case 'k':
272 case 'H':
273 /* Match hour in 24-hour clock. */
274 get_number (0, 23, 2);
275 qd->qd_hour = val;
276 have_I = 0;
277 break;
278 case 'l':
279 /* Match hour in 12-hour clock. GNU extension. */
280 case 'I':
281 /* Match hour in 12-hour clock. */
282 get_number (1, 12, 2);
283 qd->qd_hour = val % 12;
284 have_I = 1;
285 break;
286 case 'j':
287 /* Match day number of year. */
288 get_number (1, 366, 3);
289 qd->qd_yday = val - 1;
290 have_yday = 1;
291 break;
292 case 'm':
293 /* Match number of month. */
294 get_number (1, 12, 2);
295 qd->qd_mon = val;
296 have_mon = 1;
297 want_xday = 1;
298 break;
299 case 'M':
300 /* Match minute. */
301 get_number (0, 59, 2);
302 qd->qd_min = val;
303 break;
304 case 'N':
305 {
306 /* match nanoseconds */
307 gint n;
308 n = val = 0;
309 while (n < 9 && *rp >= '0' && *rp <= '9')
310 {
311 val = val * 10 + *rp++ - '0';
312 ++n;
313 }
314 qd->qd_nanosecs = val;
315 break;
316 }
317 case 'n':
318 case 't':
319 /* Match any white space. */
320 while (isspace (*rp))
321 ++rp;
322 break;
323 case 'p':
324 /* Match locale's equivalent of AM/PM. */
325 if (!match_string (HERE_AM_STR, rp))
326 {
327 if (match_string (HERE_PM_STR, rp))
328 is_pm = 1;
329 else
330 {
331 *error = ERR_LOCALE_AMPM;
332 PERR (" error=%s", QofDateErrorasString (*error));
333 return NULL;
334 }
335 }
336 break;
337 case 'r':
338 if (!recursive (HERE_T_FMT_AMPM))
339 {
340 *error = ERR_TIME_AMPM;
341 PERR (" error=%s", QofDateErrorasString (*error));
342 return NULL;
343 }
344 break;
345 case 'R':
346 if (!recursive ("%H:%M"))
347 {
348 *error = ERR_RECURSIVE_R;
349 PERR (" error=%s", QofDateErrorasString (*error));
350 return NULL;
351 }
352 break;
353 case 's':
354 {
355 /* The number of seconds may be very high so we
356 cannot use the `get_number' macro. Instead read
357 the number character for character and construct
358 the result while doing this. */
359 QofTimeSecs secs = 0;
360 if (*rp < '0' || *rp > '9')
361 /* We need at least one digit. */
362 {
363 *error = ERR_SECS_NO_DIGITS;
364 PERR (" error=%s", QofDateErrorasString (*error));
365 return NULL;
366 }
367 do
368 {
369 secs *= 10;
370 secs += *rp++ - '0';
371 }
372 while (*rp >= '0' && *rp <= '9');
374 qd->qd_sec = secs;
375 if (!qof_date_valid (qd))
376 return NULL;
377 }
378 break;
379 case 'S':
380 get_number (0, 61, 2);
381 qd->qd_sec = val;
382 break;
383 case 'X':
384 /* Fall through. */
385 case 'T':
386 if (!recursive (HERE_T_FMT))
387 {
388 *error = ERR_RECURSIVE_T;
389 PERR (" error=%s", QofDateErrorasString (*error));
390 return NULL;
391 }
392 break;
393 case 'u':
394 get_number (1, 7, 1);
395 qd->qd_wday = val % 7;
396 have_wday = 1;
397 break;
398 case 'g':
399 get_number (0, 99, 2);
400 /* XXX This cannot determine any field in TM. */
401 break;
402 case 'G':
403 if (*rp < '0' || *rp > '9')
404 {
405 *error = ERR_G_INCOMPLETE;
406 PERR (" error=%s", QofDateErrorasString (*error));
407 return NULL;
408 }
409 /* XXX Ignore the number since we would need
410 some more information to compute a real date. */
411 do
412 ++rp;
413 while (*rp >= '0' && *rp <= '9');
414 break;
415 case 'U':
416 get_number (0, 53, 2);
417 week_no = val;
418 have_uweek = 1;
419 break;
420 case 'W':
421 get_number (0, 53, 2);
422 week_no = val;
423 have_wweek = 1;
424 break;
425 case 'V':
426 get_number (0, 53, 2);
427 /* XXX This cannot determine any field without some
428 information. */
429 break;
430 case 'w':
431 /* Match number of weekday. */
432 get_number (0, 6, 1);
433 qd->qd_wday = val;
434 have_wday = 1;
435 break;
436 case 'y':
437 /* Match year within century. */
438 get_number (0, 99, 2);
439 /* The "Year 2000: The Millennium Rollover" paper suggests that
440 values in the range 69-99 refer to the twentieth century. */
441 qd->qd_year = val >= 69 ? val + 2000 : val + 1900;
442 /* Indicate that we want to use the century, if specified. */
443 want_century = 1;
444 want_xday = 1;
445 break;
446 case 'Y':
447 /* Match year including century number. */
448 get_number (0, 999999999, 9);
449 qd->qd_year = val;
450 want_century = 0;
451 want_xday = 1;
452 break;
453 case 'Z':
454 /* XXX How to handle this? */
455 PINFO (" Z format - todo?");
456 break;
457 case 'z':
458 /* We recognize two formats: if two digits are given, these
459 specify hours. If fours digits are used, minutes are
460 also specified. */
461 {
462 gboolean neg;
463 gint n;
464 val = 0;
465 while (*rp == ' ')
466 ++rp;
467 if (*rp != '+' && *rp != '-')
468 {
469 *error = ERR_INVALID_Z;
470 PERR (" error=%s", QofDateErrorasString (*error));
471 return NULL;
472 }
473 neg = *rp++ == '-';
474 n = 0;
475 while (n < 4 && *rp >= '0' && *rp <= '9')
476 {
477 val = val * 10 + *rp++ - '0';
478 ++n;
479 }
480 if (n == 2)
481 val *= 100;
482 else if (n != 4)
483 {
484 /* Only two or four digits recognized. */
485 *error = ERR_YEAR_DIGITS;
486 PERR (" error=%s", QofDateErrorasString (*error));
487 return NULL;
488 }
489 else
490 {
491 /* We have to convert the minutes into decimal. */
492 if (val % 100 >= 60)
493 {
494 *error = ERR_MIN_TO_DECIMAL;
495 PERR (" error=%s", QofDateErrorasString (*error));
496 return NULL;
497 }
498 val = (val / 100) * 100 + ((val % 100) * 50) / 30;
499 }
500 if (val > 1200)
501 {
502 *error = ERR_GMTOFF;
503 PERR (" error=%s", QofDateErrorasString (*error));
504 return NULL;
505 }
506 qd->qd_gmt_off = (val * 3600) / 100;
507 if (neg)
508 qd->qd_gmt_off = -qd->qd_gmt_off;
509 }
510 break;
511 case 'E':
512 /* We have no information about the era format.
513 Just use the normal format. */
514 if (*fmt != 'c' && *fmt != 'C' && *fmt != 'y' && *fmt != 'Y'
515 && *fmt != 'x' && *fmt != 'X')
516 {
517 /* This is an illegal format. */
518 *error = ERR_INVALID_FORMAT;
519 PERR (" error=%s", QofDateErrorasString (*error));
520 return NULL;
521 }
522
523 goto start_over;
524 case 'O':
525 switch (*fmt++)
526 {
527 case 'd':
528 case 'e':
529 /* Match day of month using alternate numeric symbols. */
530 get_alt_number (1, 31, 2);
531 qd->qd_mday = val;
532 have_mday = 1;
533 want_xday = 1;
534 break;
535 case 'H':
536 /* Match hour in 24-hour clock using alternate
537 numeric symbols. */
538 get_alt_number (0, 23, 2);
539 qd->qd_hour = val;
540 have_I = 0;
541 break;
542 case 'I':
543 /* Match hour in 12-hour clock using alternate
544 numeric symbols. */
545 get_alt_number (1, 12, 2);
546 qd->qd_hour = val % 12;
547 have_I = 1;
548 break;
549 case 'm':
550 /* Match month using alternate numeric symbols. */
551 get_alt_number (1, 12, 2);
552 qd->qd_mon = val - 1;
553 have_mon = 1;
554 want_xday = 1;
555 break;
556 case 'M':
557 /* Match minutes using alternate numeric symbols. */
558 get_alt_number (0, 59, 2);
559 qd->qd_min = val;
560 break;
561 case 'S':
562 /* Match seconds using alternate numeric symbols. */
563 get_alt_number (0, 61, 2);
564 qd->qd_sec = val;
565 break;
566 case 'U':
567 get_alt_number (0, 53, 2);
568 week_no = val;
569 have_uweek = 1;
570 break;
571 case 'W':
572 get_alt_number (0, 53, 2);
573 week_no = val;
574 have_wweek = 1;
575 break;
576 case 'V':
577 get_alt_number (0, 53, 2);
578 /* XXX This cannot determine any field without
579 further information. */
580 break;
581 case 'w':
582 /* Match number of weekday using alternate numeric symbols. */
583 get_alt_number (0, 6, 1);
584 qd->qd_wday = val;
585 have_wday = 1;
586 break;
587 case 'y':
588 /* Match year within century using alternate numeric symbols. */
589 get_alt_number (0, 99, 2);
590 qd->qd_year = val >= 69 ? val : val + 100;
591 want_xday = 1;
592 break;
593 default:
594 {
595 *error = ERR_UNKNOWN_ERR;
596 PERR (" error=%s (first default)",
597 QofDateErrorasString (*error));
598 return NULL;
599 }
600 }
601 break;
602 default:
603 {
604 *error = ERR_UNKNOWN_ERR;
605 PERR (" error=%s val=%s (second default)",
606 QofDateErrorasString (*error), rp);
607 return NULL;
608 }
609 }
610 }
611
612 if (have_I && is_pm)
613 qd->qd_hour += 12;
614
615 if (century != -1)
616 {
617 if (want_century)
619 qd->qd_year = qd->qd_year % 100 + (century - 19) * 100;
620 else
621 /* Only the century, but not the year. */
622 qd->qd_year = (century - 19) * 100;
623 }
624
625 if (era_cnt != -1)
626 {
627 if (era == NULL)
628 {
629 *error = ERR_INVALID_ERA;
630 PERR (" error=%s", QofDateErrorasString (*error));
631 return NULL;
632 }
633 }
634 else if (want_era)
635 {
636 /* No era found but we have seen an E modifier.
637 Rectify some values. */
639 if (want_century && century == -1 && qd->qd_year < 69)
640 qd->qd_year += 100;
641 }
642
643 if (want_xday && !have_wday)
644 {
645 if (!(have_mon && have_mday) && have_yday)
646 {
647 /* We don't have qd_mon and/or qd_mday, compute them. */
648 gint t_mon = 0;
649 gint leap = qof_date_isleap (qd->qd_year);
650 while (yeardays[leap][t_mon] <=
651 qd->qd_yday)
652 t_mon++;
653 if (!have_mon)
654 qd->qd_mon = t_mon;
655 if (!have_mday)
656 qd->qd_mday = qd->qd_yday -
657 yeardays[leap][t_mon - 1] + 1;
658 }
659 set_day_of_the_week (qd);
660 }
661
662 if (want_xday && !have_yday)
663 qd->qd_yday = qof_date_get_yday (qd->qd_mday,
664 qd->qd_mon, qd->qd_year);
665
666 if ((have_uweek || have_wweek) && have_wday)
667 {
668 gint save_wday = qd->qd_wday;
669 gint save_mday = qd->qd_mday;
670 gint save_mon = qd->qd_mon;
671 gint w_offset = have_uweek ? 0 : 1;
672
673 qd->qd_mday = 1;
674 qd->qd_mon = 0;
675 set_day_of_the_week (qd);
676 if (have_mday)
677 qd->qd_mday = save_mday;
678 if (have_mon)
679 qd->qd_mon = save_mon;
680
681 if (!have_yday)
682 qd->qd_yday = ((7 - (qd->qd_wday - w_offset)) % 7
683 + (week_no - 1) * 7 + save_wday - w_offset);
684
685 if (!have_mday || !have_mon)
686 {
687 gint t_mon = 0;
688
689 while (qof_date_get_yday (1, t_mon, qd->qd_year) <=
690 qd->qd_yday)
691 t_mon++;
692 if (!have_mon)
693 qd->qd_mon = t_mon - 1;
694 if (!have_mday)
695 qd->qd_mday = (qd->qd_yday -
696 qof_date_get_yday (1, t_mon, qd->qd_year));
697 }
698
699 qd->qd_wday = save_wday;
700 }
701
702 return (gchar *) rp;
703 }
QofDate_s::qd_yday
gshort qd_yday
Definition: qofdate.h:189
QofDate_s::qd_year
gint64 qd_year
Extended version to cope with full range of dates.
Definition: qofdate.h:181
PERR
#define PERR(format, args...)
Definition: qoflog.h:183
PINFO
#define PINFO(format, args...)
Definition: qoflog.h:199
QOF_MOD_DATE
#define QOF_MOD_DATE
Definition: qofdate.h:98
QofDate_s
Full range replacement for struct tm.
Definition: qofdate.h:138
QofDate_s::qd_hour
glong qd_hour
Signed replacement of struct tm.tm_hour.
Definition: qofdate.h:157
QofDate_s::qd_min
glong qd_min
Signed replacement of struct tm.tm_min.
Definition: qofdate.h:150
QofDate_s::qd_mon
glong qd_mon
Signed replacement of struct tm.tm_mon.
Definition: qofdate.h:171
qof_date_get_yday
guint16 qof_date_get_yday(gint mday, gint month, gint64 year)
Definition: qofdate.c:167
qof_date_isleap
#define qof_date_isleap(year)
Definition: qofdate.h:222
QofDate_s::qd_sec
gint64 qd_sec
Definition: qofdate.h:143
QofDate_s::qd_gmt_off
glong qd_gmt_off
Calculated value based on struct tm.tm_gmtoff.
Definition: qofdate.h:201
QofDate_s::qd_nanosecs
glong qd_nanosecs
Definition: qofdate.h:141
QofTimeSecs
gint64 QofTimeSecs
Replacement for time_t.
Definition: qoftime.h:129
QofDate_s::qd_wday
gshort qd_wday
Definition: qofdate.h:185
QofDate_s::qd_mday
glong qd_mday
Signed replacement of struct tm.tm_mday.
Definition: qofdate.h:164
qof_date_valid
gboolean qof_date_valid(QofDate *date)
Validate a QofDate.
Definition: qofdate.c:616
QofLogModule
const gchar * QofLogModule
Definition: qofid.h:85