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