QOF  0.8.7
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 * G_GNUC_UNUSED 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, G_GNUC_UNUSED 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  } while (*rp >= '0' && *rp <= '9');
372  qd->qd_sec = secs;
373  /* 's' is an epoch format */
374  qd->qd_year = 1970;
375  }
376  break;
377  case 'S':
378  get_number (0, 61, 2);
379  qd->qd_sec = val;
380  break;
381  case 'X':
382  /* Fall through. */
383  case 'T':
384  if (!recursive (HERE_T_FMT))
385  {
386  *error = ERR_RECURSIVE_T;
387  PERR (" error=%s", QofDateErrorasString (*error));
388  return NULL;
389  }
390  break;
391  case 'u':
392  get_number (1, 7, 1);
393  qd->qd_wday = val % 7;
394  have_wday = 1;
395  break;
396  case 'g':
397  get_number (0, 99, 2);
398  /* XXX This cannot determine any field in TM. */
399  break;
400  case 'G':
401  if (*rp < '0' || *rp > '9')
402  {
403  *error = ERR_G_INCOMPLETE;
404  PERR (" error=%s", QofDateErrorasString (*error));
405  return NULL;
406  }
407  /* XXX Ignore the number since we would need
408  some more information to compute a real date. */
409  do
410  ++rp;
411  while (*rp >= '0' && *rp <= '9');
412  break;
413  case 'U':
414  get_number (0, 53, 2);
415  week_no = val;
416  have_uweek = 1;
417  break;
418  case 'W':
419  get_number (0, 53, 2);
420  week_no = val;
421  have_wweek = 1;
422  break;
423  case 'V':
424  get_number (0, 53, 2);
425  /* XXX This cannot determine any field without some
426  information. */
427  break;
428  case 'w':
429  /* Match number of weekday. */
430  get_number (0, 6, 1);
431  qd->qd_wday = val;
432  have_wday = 1;
433  break;
434  case 'y':
435  /* Match year within century. */
436  get_number (0, 99, 2);
437  /* The "Year 2000: The Millennium Rollover" paper suggests that
438  values in the range 69-99 refer to the twentieth century. */
439  qd->qd_year = val >= 69 ? val + 2000 : val + 1900;
440  /* Indicate that we want to use the century, if specified. */
441  want_century = 1;
442  want_xday = 1;
443  break;
444  case 'Y':
445  /* Match year including century number. */
446  get_number (0, 999999999, 9);
447  qd->qd_year = val;
448  want_century = 0;
449  want_xday = 1;
450  break;
451  case 'Z':
452  /* XXX How to handle this? */
453  PINFO (" Z format - todo?");
454  break;
455  case 'z':
456  /* We recognize two formats: if two digits are given, these
457  specify hours. If fours digits are used, minutes are
458  also specified. */
459  {
460  gboolean neg;
461  gint n;
462  val = 0;
463  while (*rp == ' ')
464  ++rp;
465  if (*rp != '+' && *rp != '-')
466  {
467  *error = ERR_INVALID_Z;
468  PERR (" error=%s", QofDateErrorasString (*error));
469  return NULL;
470  }
471  neg = *rp++ == '-';
472  n = 0;
473  while (n < 4 && *rp >= '0' && *rp <= '9')
474  {
475  val = val * 10 + *rp++ - '0';
476  ++n;
477  }
478  if (n == 2)
479  val *= 100;
480  else if (n != 4)
481  {
482  /* Only two or four digits recognized. */
483  *error = ERR_YEAR_DIGITS;
484  PERR (" error=%s", QofDateErrorasString (*error));
485  return NULL;
486  }
487  else
488  {
489  /* We have to convert the minutes into decimal. */
490  if (val % 100 >= 60)
491  {
492  *error = ERR_MIN_TO_DECIMAL;
493  PERR (" error=%s", QofDateErrorasString (*error));
494  return NULL;
495  }
496  val = (val / 100) * 100 + ((val % 100) * 50) / 30;
497  }
498  if (val > 1200)
499  {
500  *error = ERR_GMTOFF;
501  PERR (" error=%s", QofDateErrorasString (*error));
502  return NULL;
503  }
504  qd->qd_gmt_off = (val * 3600) / 100;
505  if (neg)
506  qd->qd_gmt_off = -qd->qd_gmt_off;
507  }
508  break;
509  case 'E':
510  /* We have no information about the era format.
511  Just use the normal format. */
512  if (*fmt != 'c' && *fmt != 'C' && *fmt != 'y' && *fmt != 'Y'
513  && *fmt != 'x' && *fmt != 'X')
514  {
515  /* This is an illegal format. */
516  *error = ERR_INVALID_FORMAT;
517  PERR (" error=%s", QofDateErrorasString (*error));
518  return NULL;
519  }
520 
521  goto start_over;
522  case 'O':
523  switch (*fmt++)
524  {
525  case 'd':
526  case 'e':
527  /* Match day of month using alternate numeric symbols. */
528  get_alt_number (1, 31, 2);
529  qd->qd_mday = val;
530  have_mday = 1;
531  want_xday = 1;
532  break;
533  case 'H':
534  /* Match hour in 24-hour clock using alternate
535  numeric symbols. */
536  get_alt_number (0, 23, 2);
537  qd->qd_hour = val;
538  have_I = 0;
539  break;
540  case 'I':
541  /* Match hour in 12-hour clock using alternate
542  numeric symbols. */
543  get_alt_number (1, 12, 2);
544  qd->qd_hour = val % 12;
545  have_I = 1;
546  break;
547  case 'm':
548  /* Match month using alternate numeric symbols. */
549  get_alt_number (1, 12, 2);
550  qd->qd_mon = val - 1;
551  have_mon = 1;
552  want_xday = 1;
553  break;
554  case 'M':
555  /* Match minutes using alternate numeric symbols. */
556  get_alt_number (0, 59, 2);
557  qd->qd_min = val;
558  break;
559  case 'S':
560  /* Match seconds using alternate numeric symbols. */
561  get_alt_number (0, 61, 2);
562  qd->qd_sec = val;
563  break;
564  case 'U':
565  get_alt_number (0, 53, 2);
566  week_no = val;
567  have_uweek = 1;
568  break;
569  case 'W':
570  get_alt_number (0, 53, 2);
571  week_no = val;
572  have_wweek = 1;
573  break;
574  case 'V':
575  get_alt_number (0, 53, 2);
576  /* XXX This cannot determine any field without
577  further information. */
578  break;
579  case 'w':
580  /* Match number of weekday using alternate numeric symbols. */
581  get_alt_number (0, 6, 1);
582  qd->qd_wday = val;
583  have_wday = 1;
584  break;
585  case 'y':
586  /* Match year within century using alternate numeric symbols. */
587  get_alt_number (0, 99, 2);
588  qd->qd_year = val >= 69 ? val : val + 100;
589  want_xday = 1;
590  break;
591  default:
592  {
593  *error = ERR_UNKNOWN_ERR;
594  PERR (" error=%s (first default)",
595  QofDateErrorasString (*error));
596  return NULL;
597  }
598  }
599  break;
600  default:
601  {
602  *error = ERR_UNKNOWN_ERR;
603  PERR (" error=%s val=%s (second default)",
604  QofDateErrorasString (*error), rp);
605  return NULL;
606  }
607  }
608  }
609 
610  if (have_I && is_pm)
611  qd->qd_hour += 12;
612 
613  if (century != -1)
614  {
615  if (want_century)
617  qd->qd_year = qd->qd_year % 100 + (century - 19) * 100;
618  else
619  /* Only the century, but not the year. */
620  qd->qd_year = (century - 19) * 100;
621  }
622 
623  if (era_cnt != -1)
624  {
625  if (era == NULL)
626  {
627  *error = ERR_INVALID_ERA;
628  PERR (" error=%s", QofDateErrorasString (*error));
629  return NULL;
630  }
631  }
632  else if (want_era)
633  {
634  /* No era found but we have seen an E modifier.
635  Rectify some values. */
637  if (want_century && century == -1 && qd->qd_year < 69)
638  qd->qd_year += 100;
639  }
640 
641  if (want_xday && !have_wday)
642  {
643  if (!(have_mon && have_mday) && have_yday)
644  {
645  /* We don't have qd_mon and/or qd_mday, compute them. */
646  gint t_mon = 0;
647  gint leap = qof_date_isleap (qd->qd_year);
648  while (yeardays[leap][t_mon] <=
649  qd->qd_yday)
650  t_mon++;
651  if (!have_mon)
652  qd->qd_mon = t_mon;
653  if (!have_mday)
654  qd->qd_mday = qd->qd_yday -
655  yeardays[leap][t_mon - 1] + 1;
656  }
657  set_day_of_the_week (qd);
658  }
659 
660  if (want_xday && !have_yday)
661  qd->qd_yday = qof_date_get_yday (qd->qd_mday,
662  qd->qd_mon, qd->qd_year);
663 
664  if ((have_uweek || have_wweek) && have_wday)
665  {
666  gint save_wday = qd->qd_wday;
667  gint save_mday = qd->qd_mday;
668  gint save_mon = qd->qd_mon;
669  gint w_offset = have_uweek ? 0 : 1;
670 
671  qd->qd_mday = 1;
672  qd->qd_mon = 0;
673  set_day_of_the_week (qd);
674  if (have_mday)
675  qd->qd_mday = save_mday;
676  if (have_mon)
677  qd->qd_mon = save_mon;
678 
679  if (!have_yday)
680  qd->qd_yday = ((7 - (qd->qd_wday - w_offset)) % 7
681  + (week_no - 1) * 7 + save_wday - w_offset);
682 
683  if (!have_mday || !have_mon)
684  {
685  gint t_mon = 0;
686 
687  while (qof_date_get_yday (1, t_mon, qd->qd_year) <=
688  qd->qd_yday)
689  t_mon++;
690  if (!have_mon)
691  qd->qd_mon = t_mon - 1;
692  if (!have_mday)
693  qd->qd_mday = (qd->qd_yday -
694  qof_date_get_yday (1, t_mon, qd->qd_year));
695  }
696 
697  qd->qd_wday = save_wday;
698  }
699 
700  return (gchar *) rp;
701 }
gshort qd_yday
Definition: qofdate.h:189
gint64 qd_year
Extended version to cope with full range of dates.
Definition: qofdate.h:181
#define PERR(format, args...)
Definition: qoflog.h:183
#define PINFO(format, args...)
Definition: qoflog.h:199
#define QOF_MOD_DATE
Definition: qofdate.h:98
Full range replacement for struct tm.
Definition: qofdate.h:138
glong qd_hour
Signed replacement of struct tm.tm_hour.
Definition: qofdate.h:157
glong qd_min
Signed replacement of struct tm.tm_min.
Definition: qofdate.h:150
glong qd_mon
Signed replacement of struct tm.tm_mon.
Definition: qofdate.h:171
guint16 qof_date_get_yday(gint mday, gint month, gint64 year)
Definition: qofdate.c:167
#define qof_date_isleap(year)
Definition: qofdate.h:222
gint64 qd_sec
Definition: qofdate.h:143
glong qd_gmt_off
Calculated value based on struct tm.tm_gmtoff.
Definition: qofdate.h:201
glong qd_nanosecs
Definition: qofdate.h:141
gint64 QofTimeSecs
Replacement for time_t.
Definition: qoftime.h:121
gshort qd_wday
Definition: qofdate.h:185
glong qd_mday
Signed replacement of struct tm.tm_mday.
Definition: qofdate.h:164
const gchar * QofLogModule
Definition: qofid.h:85