CXXR (C++ R)
Rstrptime.h
1 /*CXXR $Id: Rstrptime.h 1348 2013-02-25 17:49:03Z arr $
2  *CXXR
3  *CXXR This file is part of CXXR, a project to refactor the R interpreter
4  *CXXR into C++. It may consist in whole or in part of program code and
5  *CXXR documentation taken from the R project itself, incorporated into
6  *CXXR CXXR (and possibly MODIFIED) under the terms of the GNU General Public
7  *CXXR Licence.
8  *CXXR
9  *CXXR CXXR is Copyright (C) 2008-13 Andrew R. Runnalls, subject to such other
10  *CXXR copyrights and copyright restrictions as may be stated below.
11  *CXXR
12  *CXXR CXXR is not part of the R project, and bugs and other issues should
13  *CXXR not be reported via r-bugs or other R project channels; instead refer
14  *CXXR to the CXXR website.
15  *CXXR */
16 
17 /* For inclusion by datetime.cpp.
18 
19  A modified version of code from the GNU C library with locale
20  support removed and wchar support added.
21 */
22 
23 #ifndef RSTRPTIME_H
24 #define RSTRPTIME_H 1
25 
26 #ifdef __cplusplus
27 extern "C" {
28 #endif
29 
30 /* Convert a string representation of time to a time value.
31  Copyright (C) 1996, 1997, 1998, 1999, 2000 Free Software Foundation, Inc.
32  This file is part of the GNU C Library.
33  Contributed by Ulrich Drepper <drepper@cygnus.com>, 1996.
34 
35  The GNU C Library is free software; you can redistribute it and/or
36  modify it under the terms of the GNU Library General Public License as
37  published by the Free Software Foundation; either version 2 of the
38  License, or (at your option) any later version.
39 
40  The GNU C Library is distributed in the hope that it will be useful,
41  but WITHOUT ANY WARRANTY; without even the implied warranty of
42  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
43  Library General Public License for more details.
44 
45  You should have received a copy of the GNU Library General Public
46  License along with the GNU C Library; see the file COPYING.LIB. If not,
47  a copy is available at http://www.r-project.org/licenses/
48 */
49 /* XXX This version of the implementation is not really complete.
50  Some of the fields cannot add information alone. But if seeing
51  some of them in the same format (such as year, week and weekday)
52  this is enough information for determining the date. */
53 
54 /* #include <ctype.h>
55 #include <limits.h>
56 #include <string.h>*/
57 
58 /* This is C90 */
59 #ifndef HAVE_LOCALE_H
60 # define HAVE_LOCALE_H 1
61 #endif
62 #ifdef HAVE_STRINGS_H
63 #include <strings.h> /* for strncasecmp */
64 #endif
65 
66 #include <ctype.h> /* for isspace */
67 
68 #define match_char(ch1, ch2) if (ch1 != ch2) return NULL
69 
70 /* we guarantee to have strncasecmp in R */
71 #if defined __GNUC__ && __GNUC__ >= 2
72 # define match_string(cs1, s2) \
73  (__extension__ ({ size_t len = strlen (cs1); \
74  int result = strncasecmp ((cs1), (s2), len) == 0; \
75  if (result) (s2) += len; \
76  result; }))
77 #else
78 /* Oh come on. Get a reasonable compiler. */
79 # define match_string(cs1, s2) \
80  (strncasecmp ((cs1), (s2), strlen (cs1)) ? 0 : ((s2) += strlen (cs1), 1))
81 #endif
82 
83 /* We intentionally do not use isdigit() for testing because this will
84  lead to problems with the wide character version. */
85 #define get_number(from, to, n) \
86  do { \
87  int __n = n; \
88  val = 0; \
89  while (*rp == ' ') \
90  ++rp; \
91  if (*rp < '0' || *rp > '9') \
92  return NULL; \
93  do { \
94  val *= 10; \
95  val += *rp++ - '0'; \
96 /* } while (--__n > 0 && val * 10 <= to && *rp >= '0' && *rp <= '9');*/ \
97  } while (--__n > 0 && *rp >= '0' && *rp <= '9'); \
98  if (int(val) < from || val > to) \
99  return NULL; \
100  } while (0)
101 # define get_alt_number(from, to, n) \
102  /* We don't have the alternate representation. */ \
103  get_number(from, to, n)
104 #define recursive(new_fmt) \
105  (*(new_fmt) != '\0' \
106  && (rp = strptime_internal (rp, (new_fmt), tm, decided, psecs, poffset)) != NULL)
107 
108 /* This version: may overwrite these with versions for the locale,
109  * hence the extra length of the fields
110  */
111 static char weekday_name[][20] =
112 {
113  "Sunday", "Monday", "Tuesday", "Wednesday",
114  "Thursday", "Friday", "Saturday"
115 };
116 static char ab_weekday_name[][10] =
117 {
118  "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
119 };
120 static char month_name[][20] =
121 {
122  "January", "February", "March", "April", "May", "June",
123  "July", "August", "September", "October", "November", "December"
124 };
125 static char ab_month_name[][10] =
126 {
127  "Jan", "Feb", "Mar", "Apr", "May", "Jun",
128  "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
129 };
130 
131 static char am_pm[][4] = {"AM", "PM"};
132 
133 
134 # define HERE_D_T_FMT "%a %b %e %H:%M:%S %Y"
135 # define HERE_D_FMT "%y/%m/%d"
136 # define HERE_T_FMT_AMPM "%I:%M:%S %p"
137 # define HERE_T_FMT "%H:%M:%S"
138 
139 static const unsigned short int __mon_yday[2][13] =
140 {
141  /* Normal years. */
142  { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 },
143  /* Leap years. */
144  { 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366 }
145 };
146 
147 
148 /* Status of lookup: do we use the locale data or the raw data? */
149 enum locale_status { Not, loc, raw };
150 
151 # define __isleap(year) \
152  ((year) % 4 == 0 && ((year) % 100 != 0 || (year) % 400 == 0))
153 
154 /* Compute the day of the week. */
155 static void
156 day_of_the_week (struct tm *tm)
157 {
158  /* We know that January 1st 1970 was a Thursday (= 4). Compute the
159  the difference between this data in the one on TM and so determine
160  the weekday. */
161  int corr_year, wday;
162 
163  /* R bug fix: day_of_the_week needs year, month, mday set */
164  if(tm->tm_year == NA_INTEGER ||
165  tm->tm_mon == NA_INTEGER ||
166  tm->tm_mday == NA_INTEGER) return;
167 
168  corr_year = 1900 + tm->tm_year - (tm->tm_mon < 2);
169  wday = (-473
170  + (365 * (tm->tm_year - 70))
171  + (corr_year / 4)
172  - ((corr_year / 4) / 25) + ((corr_year / 4) % 25 < 0)
173  + (((corr_year / 4) / 25) / 4)
174  + __mon_yday[0][tm->tm_mon]
175  + tm->tm_mday - 1);
176  tm->tm_wday = ((wday % 7) + 7) % 7;
177 }
178 
179 /* Compute the day of the year. */
180 static void
181 day_of_the_year (struct tm *tm)
182 {
183  /* R bug fix: day_of_the_year needs year, month, mday set */
184  if(tm->tm_year == NA_INTEGER ||
185  tm->tm_mon == NA_INTEGER ||
186  tm->tm_mday == NA_INTEGER) return;
187 
188  tm->tm_yday = (__mon_yday[__isleap (1900 + tm->tm_year)][tm->tm_mon]
189  + (tm->tm_mday - 1));
190 }
191 
192 #include <wchar.h>
193 #include <wctype.h>
194 
195 static wchar_t w_weekday_name[][20] =
196 {
197  L"Sunday", L"Monday", L"Tuesday", L"Wednesday",
198  L"Thursday", L"Friday", L"Saturday"
199 };
200 static wchar_t w_ab_weekday_name[][10] =
201 {
202  L"Sun", L"Mon", L"Tue", L"Wed", L"Thu", L"Fri", L"Sat"
203 };
204 static wchar_t w_month_name[][20] =
205 {
206  L"January", L"February", L"March", L"April", L"May", L"June",
207  L"July", L"August", L"September", L"October", L"November", L"December"
208 };
209 static wchar_t w_ab_month_name[][10] =
210 {
211  L"Jan", L"Feb", L"Mar", L"Apr", L"May", L"Jun",
212  L"Jul", L"Aug", L"Sep", L"Oct", L"Nov", L"Dec"
213 };
214 
215 static wchar_t w_am_pm[][4] = {L"AM", L"PM"};
216 
217 /* Need case-insensitive version */
218 static int Rwcsncasecmp(const wchar_t *cs1, const wchar_t *s2)
219 {
220  size_t i, n = wcslen(cs1);
221  const wchar_t *a = cs1, *b = s2;
222  for(i = 0; i < n; i++, a++, b++) {
223  if(*b == L'\0' || towlower(*a) != towlower(*b)) return 1;
224  }
225  return 0;
226 }
227 
228 #define w_match_string(cs1, s2) \
229  (Rwcsncasecmp ((cs1), (s2)) ? 0 : ((s2) += wcslen (cs1), 1))
230 
231 #define w_recursive(new_fmt) \
232  (*(new_fmt) != '\0' \
233  && (rp = w_strptime_internal (rp, (new_fmt), tm, decided, psecs, poffset)) != NULL)
234 
235 static wchar_t *
236 w_strptime_internal (wchar_t *rp, const wchar_t *fmt, struct tm *tm,
237  enum locale_status *decided, double *psecs,
238  int *poffset)
239 {
240  int cnt;
241  int val;
242  int have_I, is_pm;
243  int century, want_century;
244  int have_wday, want_xday;
245  int have_yday;
246  int have_mon, have_mday;
247  int have_uweek, have_wweek;
248  int week_no = 0; /* -Wall */
249 
250  have_I = is_pm = 0;
251  century = -1;
252  want_century = 0;
253  have_wday = want_xday = have_yday = have_mon = have_mday = 0;
254  have_uweek = have_wweek = 0;
255 
256  while (*fmt != L'\0')
257  {
258  /* A white space in the format string matches 0 more or white
259  space in the input string. */
260  if (iswspace (*fmt))
261  {
262  while (iswspace (*rp))
263  ++rp;
264  ++fmt;
265  continue;
266  }
267 
268  /* Any character but `%' must be matched by the same character
269  in the input string. */
270  if (*fmt != L'%')
271  {
272  match_char (*fmt++, *rp++);
273  continue;
274  }
275 
276  ++fmt;
277 
278  /* We need this for handling the `E' modifier. */
279  start_over:
280 
281  switch (*fmt++)
282  {
283  case L'%':
284  /* Match the `%' character itself. */
285  match_char (L'%', *rp++);
286  break;
287  case L'a':
288  case L'A':
289  /* Match day of week. */
290  for (cnt = 0; cnt < 7; ++cnt)
291  {
292  if (*decided != loc
293  && (w_match_string (w_weekday_name[cnt], rp)
294  || w_match_string (w_ab_weekday_name[cnt], rp)))
295  {
296  *decided = raw;
297  break;
298  }
299  }
300  if (cnt == 7)
301  /* Does not match a weekday name. */
302  return NULL;
303  tm->tm_wday = cnt;
304  have_wday = 1;
305  break;
306  case L'b':
307  case L'B':
308  case L'h':
309  /* Match month name. */
310  for (cnt = 0; cnt < 12; ++cnt)
311  {
312  if (w_match_string (w_month_name[cnt], rp)
313  || w_match_string (w_ab_month_name[cnt], rp))
314  {
315  *decided = raw;
316  break;
317  }
318  }
319  if (cnt == 12)
320  /* Does not match a month name. */
321  return NULL;
322  tm->tm_mon = cnt;
323  want_xday = 1;
324  break;
325  case L'c':
326  /* Match locale's date and time format. */
327  if (!w_recursive (L"%a %b %e %H:%M:%S %Y")) /* HERE_D_T_FMT */
328  return NULL;
329  break;
330  case L'C':
331  /* Match century number. */
332  get_number (0, 99, 2);
333  century = val;
334  want_xday = 1;
335  break;
336  case L'd':
337  case L'e':
338  /* Match day of month. */
339  get_number (1, 31, 2);
340  tm->tm_mday = val;
341  have_mday = 1;
342  want_xday = 1;
343  break;
344  case L'F':
345  if (!w_recursive (L"%Y-%m-%d"))
346  return NULL;
347  want_xday = 1;
348  break;
349  case L'x':
350  /* Fall through. */
351  case L'D':
352  /* Match standard day format. */
353  if (!w_recursive (L"%y/%m/%d")) /* HERE_D_FMT */
354  return NULL;
355  want_xday = 1;
356  break;
357  case L'k':
358  case L'H':
359  /* Match hour in 24-hour clock. */
360  get_number (0, 24, 2); /* allow 24:00:00 */
361  tm->tm_hour = val;
362  have_I = 0;
363  break;
364  case L'l':
365  /* Match hour in 12-hour clock. GNU extension. */
366  case L'I':
367  /* Match hour in 12-hour clock. */
368  get_number (1, 12, 2);
369  tm->tm_hour = val % 12;
370  have_I = 1;
371  break;
372  case L'j':
373  /* Match day number of year. */
374  get_number (1, 366, 3);
375  tm->tm_yday = val - 1;
376  have_yday = 1;
377  break;
378  case L'm':
379  /* Match number of month. */
380  get_number (1, 12, 2);
381  tm->tm_mon = val - 1;
382  have_mon = 1;
383  want_xday = 1;
384  break;
385  case L'M':
386  /* Match minute. */
387  get_number (0, 59, 2);
388  tm->tm_min = val;
389  break;
390  case L'n':
391  case L't':
392  /* Match any white space. */
393  while (iswspace (*rp))
394  ++rp;
395  break;
396  case L'p':
397  /* Match locale's equivalent of AM/PM. */
398  if (!w_match_string (w_am_pm[0], rp)) {
399  if (w_match_string (w_am_pm[1], rp))
400  is_pm = 1;
401  else
402  return NULL;
403  }
404  break;
405  case L'r':
406  if (!w_recursive (L"%I:%M:%S %p")) /* HERE_T_FMT_AMPM */
407  return NULL;
408  break;
409  case L'R':
410  if (!w_recursive (L"%H:%M"))
411  return NULL;
412  break;
413  case L's':
414  {
415  /* The number of seconds may be very high so we cannot use
416  the `get_number' macro. Instead read the number
417  character for character and construct the result while
418  doing this. */
419  time_t secs = 0;
420  if (*rp < L'0' || *rp > L'9')
421  /* We need at least one digit. */
422  return NULL;
423 
424  do
425  {
426  secs *= 10;
427  secs += *rp++ - L'0';
428  }
429  while (*rp >= L'0' && *rp <= L'9');
430 
431  if ((tm = localtime (&secs)) == NULL)
432  /* Error in function. */
433  return NULL;
434  }
435  break;
436  case L'S':
437  get_number (0, 61, 2);
438  tm->tm_sec = val;
439  break;
440  case L'X':
441  /* Fall through. */
442  case L'T':
443  if (!w_recursive (L"%H:%M:%S")) /* HERE_T_FMT */
444  return NULL;
445  break;
446  case L'u':
447  get_number (1, 7, 1);
448  tm->tm_wday = val % 7;
449  have_wday = 1;
450  break;
451  case L'g':
452  get_number (0, 99, 2);
453  /* XXX This cannot determine any field in TM. */
454  break;
455  case L'G':
456  if (*rp < L'0' || *rp > L'9')
457  return NULL;
458  /* XXX Ignore the number since we would need some more
459  information to compute a real date. */
460  do
461  ++rp;
462  while (*rp >= L'0' && *rp <= L'9');
463  break;
464  case L'U':
465  get_number (0, 53, 2);
466  week_no = val;
467  have_uweek = 1;
468  break;
469  case L'W':
470  get_number (0, 53, 2);
471  week_no = val;
472  have_wweek = 1;
473  break;
474  case L'V':
475  get_number (0, 53, 2);
476  /* XXX This cannot determine any field in TM without some
477  information. */
478  break;
479  case L'w':
480  /* Match number of weekday. */
481  get_number (0, 6, 1);
482  tm->tm_wday = val;
483  have_wday = 1;
484  break;
485  case L'y':
486  /* Match year within century. */
487  get_number (0, 99, 2);
488  /* The "Year 2000: The Millennium Rollover" paper suggests that
489  values in the range 69-99 refer to the twentieth century. */
490  tm->tm_year = val >= 69 ? val : val + 100;
491  /* Indicate that we want to use the century, if specified. */
492  want_century = 1;
493  want_xday = 1;
494  break;
495  case L'Y':
496  /* Match year including century number. */
497  get_number (0, 9999, 4);
498  tm->tm_year = val - 1900;
499  want_century = 0;
500  want_xday = 1;
501  break;
502  case L'z':
503  {
504  int n = 0, neg, off = 0;
505  val = 0;
506  while (*rp == L' ') ++rp;
507  if (*rp != L'+' && *rp != L'-') return NULL;
508  neg = *rp++ == L'-';
509  while (n < 4 && *rp >= L'0' && *rp <= L'9') {
510  val = val * 10 + *rp++ - L'0';
511  ++n;
512  }
513  if (n != 4) return NULL;
514  else {
515  /* We have to convert the minutes into decimal. */
516  if (val % 100 >= 60) return NULL;
517  val = (val / 100) * 100 + ((val % 100) * 50) / 30;
518  }
519  if (val > 1200) return NULL;
520  off = (val * 3600) / 100;
521  if (neg) off = -off;
522  *poffset = off;
523  }
524  break;
525  case L'Z':
526  error(_("use of %s for input is not supported"), "%Z");
527  return NULL;
528  break;
529  case L'E':
530  /* We have no information about the era format. Just use
531  the normal format. */
532  if (*fmt != L'c' && *fmt != L'C' && *fmt != L'y' && *fmt != L'Y'
533  && *fmt != L'x' && *fmt != L'X')
534  /* This is an illegal format. */
535  return NULL;
536 
537  goto start_over;
538  case L'O':
539  switch (*fmt++)
540  {
541  case L'd':
542  case L'e':
543  /* Match day of month using alternate numeric symbols. */
544  get_alt_number (1, 31, 2);
545  tm->tm_mday = val;
546  have_mday = 1;
547  want_xday = 1;
548  break;
549  case L'H':
550  /* Match hour in 24-hour clock using alternate numeric
551  symbols. */
552  get_alt_number (0, 23, 2);
553  tm->tm_hour = val;
554  have_I = 0;
555  break;
556  case L'I':
557  /* Match hour in 12-hour clock using alternate numeric
558  symbols. */
559  get_alt_number (1, 12, 2);
560  tm->tm_hour = val % 12;
561  have_I = 1;
562  break;
563  case L'm':
564  /* Match month using alternate numeric symbols. */
565  get_alt_number (1, 12, 2);
566  tm->tm_mon = val - 1;
567  have_mon = 1;
568  want_xday = 1;
569  break;
570  case L'M':
571  /* Match minutes using alternate numeric symbols. */
572  get_alt_number (0, 59, 2);
573  tm->tm_min = val;
574  break;
575  case L'S':
576  /* Match seconds using alternate numeric symbols.
577  get_alt_number (0, 61, 2); */
578  {
579  double sval;
580  wchar_t *end;
581  sval = wcstod(rp, &end);
582  if( sval >= 0.0 && sval <= 61.0) {
583  tm->tm_sec = int(sval);
584  *psecs = sval;
585  }
586  rp = end;
587  }
588  break;
589  case L'U':
590  get_alt_number (0, 53, 2);
591  week_no = val;
592  have_uweek = 1;
593  break;
594  case L'W':
595  get_alt_number (0, 53, 2);
596  week_no = val;
597  have_wweek = 1;
598  break;
599  case L'V':
600  get_alt_number (0, 53, 2);
601  /* XXX This cannot determine any field in TM without
602  further information. */
603  break;
604  case L'w':
605  /* Match number of weekday using alternate numeric symbols. */
606  get_alt_number (0, 6, 1);
607  tm->tm_wday = val;
608  have_wday = 1;
609  break;
610  case L'y':
611  /* Match year within century using alternate numeric symbols. */
612  get_alt_number (0, 99, 2);
613  tm->tm_year = val >= 69 ? val : val + 100;
614  want_xday = 1;
615  break;
616  default:
617  return NULL;
618  }
619  break;
620  default:
621  return NULL;
622  }
623  }
624 
625  if (have_I && is_pm)
626  tm->tm_hour += 12;
627 
628  if (century != -1)
629  {
630  if (want_century)
631  tm->tm_year = tm->tm_year % 100 + (century - 19) * 100;
632  else
633  /* Only the century, but not the year. Strange, but so be it. */
634  tm->tm_year = (century - 19) * 100;
635  }
636 
637  if (want_xday && !have_wday) {
638  if ( !(have_mon && have_mday) && have_yday) {
639  /* We don't have tm_mon and/or tm_mday, compute them. */
640  int t_mon = 0;
641  while (__mon_yday[__isleap(1900 + tm->tm_year)][t_mon] <= tm->tm_yday)
642  t_mon++;
643  if (!have_mon)
644  tm->tm_mon = t_mon - 1;
645  if (!have_mday)
646  tm->tm_mday = (tm->tm_yday - __mon_yday[__isleap(1900 + tm->tm_year)][t_mon - 1] + 1);
647  }
648  day_of_the_week (tm);
649  }
650 
651  if (want_xday && !have_yday)
652  day_of_the_year (tm);
653 
654  if ((have_uweek || have_wweek) && have_wday) {
655  int save_wday = tm->tm_wday;
656  int save_mday = tm->tm_mday;
657  int save_mon = tm->tm_mon;
658  int w_offset = have_uweek ? 0 : 1;
659 
660  tm->tm_mday = 1;
661  tm->tm_mon = 0;
662  day_of_the_week (tm);
663  if (have_mday)
664  tm->tm_mday = save_mday;
665  if (have_mon)
666  tm->tm_mon = save_mon;
667 
668  if (!have_yday)
669  tm->tm_yday = ((7 - (tm->tm_wday - w_offset)) % 7
670  + (week_no - 1) *7
671  + save_wday - w_offset);
672 
673  if (!have_mday || !have_mon)
674  {
675  int t_mon = 0;
676  while (__mon_yday[__isleap(1900 + tm->tm_year)][t_mon]
677  <= tm->tm_yday)
678  t_mon++;
679  if (!have_mon)
680  tm->tm_mon = t_mon - 1;
681  if (!have_mday)
682  tm->tm_mday =
683  (tm->tm_yday
684  - __mon_yday[__isleap(1900 + tm->tm_year)][t_mon - 1] + 1);
685  }
686 
687  tm->tm_wday = save_wday;
688  }
689 
690  return rp;
691 }
692 
693 
694 static char *
695 strptime_internal (const char *rp, const char *fmt, struct tm *tm,
696  enum locale_status *decided, double *psecs,
697  int *poffset)
698 {
699  int cnt;
700  int val;
701  int have_I, is_pm;
702  int century, want_century;
703  int have_wday, want_xday;
704  int have_yday;
705  int have_mon, have_mday;
706  int have_uweek, have_wweek;
707  int week_no = 0; /* -Wall */
708 
709  have_I = is_pm = 0;
710  century = -1;
711  want_century = 0;
712  have_wday = want_xday = have_yday = have_mon = have_mday = 0;
713  have_uweek = have_wweek = 0;
714 
715  while (*fmt != '\0')
716  {
717  /* A white space in the format string matches 0 more or white
718  space in the input string. */
719  if (isspace ((int)*fmt))
720  {
721  while (isspace ((int)*rp))
722  ++rp;
723  ++fmt;
724  continue;
725  }
726 
727  /* Any character but `%' must be matched by the same character
728  in the input string. */
729  if (*fmt != '%')
730  {
731  match_char (*fmt++, *rp++);
732  continue;
733  }
734 
735  ++fmt;
736 
737  /* We need this for handling the `E' modifier. */
738  start_over:
739 
740  switch (*fmt++)
741  {
742  case '%':
743  /* Match the `%' character itself. */
744  match_char ('%', *rp++);
745  break;
746  case 'a':
747  case 'A':
748  /* Match day of week. */
749  for (cnt = 0; cnt < 7; ++cnt)
750  {
751  if (*decided != loc
752  && (match_string (weekday_name[cnt], rp)
753  || match_string (ab_weekday_name[cnt], rp)))
754  {
755  *decided = raw;
756  break;
757  }
758  }
759  if (cnt == 7)
760  /* Does not match a weekday name. */
761  return NULL;
762  tm->tm_wday = cnt;
763  have_wday = 1;
764  break;
765  case 'b':
766  case 'B':
767  case 'h':
768  /* Match month name. */
769  for (cnt = 0; cnt < 12; ++cnt)
770  {
771  if (match_string (month_name[cnt], rp)
772  || match_string (ab_month_name[cnt], rp))
773  {
774  *decided = raw;
775  break;
776  }
777  }
778  if (cnt == 12)
779  /* Does not match a month name. */
780  return NULL;
781  tm->tm_mon = cnt;
782  want_xday = 1;
783  break;
784  case 'c':
785  /* Match locale's date and time format. */
786  if (!recursive (HERE_D_T_FMT))
787  return NULL;
788  break;
789  case 'C':
790  /* Match century number. */
791  get_number (0, 99, 2);
792  century = val;
793  want_xday = 1;
794  break;
795  case 'd':
796  case 'e':
797  /* Match day of month. */
798  get_number (1, 31, 2);
799  tm->tm_mday = val;
800  have_mday = 1;
801  want_xday = 1;
802  break;
803  case 'F':
804  if (!recursive ("%Y-%m-%d"))
805  return NULL;
806  want_xday = 1;
807  break;
808  case 'x':
809  /* Fall through. */
810  case 'D':
811  /* Match standard day format. */
812  if (!recursive (HERE_D_FMT))
813  return NULL;
814  want_xday = 1;
815  break;
816  case 'k':
817  case 'H':
818  /* Match hour in 24-hour clock. */
819  get_number (0, 24, 2); /* allow 24:00:00 */
820  tm->tm_hour = val;
821  have_I = 0;
822  break;
823  case 'l':
824  /* Match hour in 12-hour clock. GNU extension. */
825  case 'I':
826  /* Match hour in 12-hour clock. */
827  get_number (1, 12, 2);
828  tm->tm_hour = val % 12;
829  have_I = 1;
830  break;
831  case 'j':
832  /* Match day number of year. */
833  get_number (1, 366, 3);
834  tm->tm_yday = val - 1;
835  have_yday = 1;
836  break;
837  case 'm':
838  /* Match number of month. */
839  get_number (1, 12, 2);
840  tm->tm_mon = val - 1;
841  have_mon = 1;
842  want_xday = 1;
843  break;
844  case 'M':
845  /* Match minute. */
846  get_number (0, 59, 2);
847  tm->tm_min = val;
848  break;
849  case 'n':
850  case 't':
851  /* Match any white space. */
852  while (isspace ((int)*rp))
853  ++rp;
854  break;
855  case 'p':
856  /* Match locale's equivalent of AM/PM. */
857  if (!match_string (am_pm[0], rp)) {
858  if (match_string (am_pm[1], rp))
859  is_pm = 1;
860  else
861  return NULL;
862  }
863  break;
864  case 'r':
865  if (!recursive (HERE_T_FMT_AMPM))
866  return NULL;
867  break;
868  case 'R':
869  if (!recursive ("%H:%M"))
870  return NULL;
871  break;
872  case 's':
873  {
874  /* The number of seconds may be very high so we cannot use
875  the `get_number' macro. Instead read the number
876  character for character and construct the result while
877  doing this. */
878  time_t secs = 0;
879  if (*rp < '0' || *rp > '9')
880  /* We need at least one digit. */
881  return NULL;
882 
883  do
884  {
885  secs *= 10;
886  secs += *rp++ - '0';
887  }
888  while (*rp >= '0' && *rp <= '9');
889 
890  if ((tm = localtime (&secs)) == NULL)
891  /* Error in function. */
892  return NULL;
893  }
894  break;
895  case 'S':
896  get_number (0, 61, 2);
897  tm->tm_sec = val;
898  break;
899  case 'X':
900  /* Fall through. */
901  case 'T':
902  if (!recursive (HERE_T_FMT))
903  return NULL;
904  break;
905  case 'u':
906  get_number (1, 7, 1);
907  tm->tm_wday = val % 7;
908  have_wday = 1;
909  break;
910  case 'g':
911  get_number (0, 99, 2);
912  /* XXX This cannot determine any field in TM. */
913  break;
914  case 'G':
915  if (*rp < '0' || *rp > '9')
916  return NULL;
917  /* XXX Ignore the number since we would need some more
918  information to compute a real date. */
919  do
920  ++rp;
921  while (*rp >= '0' && *rp <= '9');
922  break;
923  case 'U':
924  get_number (0, 53, 2);
925  week_no = val;
926  have_uweek = 1;
927  break;
928  case 'W':
929  get_number (0, 53, 2);
930  week_no = val;
931  have_wweek = 1;
932  break;
933  case 'V':
934  get_number (0, 53, 2);
935  /* XXX This cannot determine any field in TM without some
936  information. */
937  break;
938  case 'w':
939  /* Match number of weekday. */
940  get_number (0, 6, 1);
941  tm->tm_wday = val;
942  have_wday = 1;
943  break;
944  case 'y':
945  /* Match year within century. */
946  get_number (0, 99, 2);
947  /* The "Year 2000: The Millennium Rollover" paper suggests that
948  values in the range 69-99 refer to the twentieth century.
949  And this is mandated by the POSIX 2001 standard, with a
950  caveat that it might change in future.
951  */
952  tm->tm_year = val >= 69 ? val : val + 100;
953  /* Indicate that we want to use the century, if specified. */
954  want_century = 1;
955  want_xday = 1;
956  break;
957  case 'Y':
958  /* Match year including century number. */
959  get_number (0, 9999, 4);
960  tm->tm_year = val - 1900;
961  want_century = 0;
962  want_xday = 1;
963  break;
964  case 'z':
965  /* Only recognize RFC 822 form */
966  {
967  int n = 0, neg, off = 0;
968  val = 0;
969  while (*rp == ' ') ++rp;
970  if (*rp != '+' && *rp != '-') return NULL;
971  neg = *rp++ == '-';
972  while (n < 4 && *rp >= '0' && *rp <= '9') {
973  val = val * 10 + *rp++ - '0';
974  ++n;
975  }
976  if (n != 4) return NULL;
977  else {
978  /* We have to convert the minutes into decimal. */
979  if (val % 100 >= 60) return NULL;
980  val = (val / 100) * 100 + ((val % 100) * 50) / 30;
981  }
982  if (val > 1200) return NULL;
983  off = (val * 3600) / 100;
984  if (neg) off = -off;
985  *poffset = off;
986  }
987  break;
988  case 'Z':
989  error(_("use of %s for input is not supported"), "%Z");
990  return NULL;
991  break;
992  case 'E':
993  /* We have no information about the era format. Just use
994  the normal format. */
995  if (*fmt != 'c' && *fmt != 'C' && *fmt != 'y' && *fmt != 'Y'
996  && *fmt != 'x' && *fmt != 'X')
997  /* This is an illegal format. */
998  return NULL;
999 
1000  goto start_over;
1001  case 'O':
1002  switch (*fmt++)
1003  {
1004  case 'd':
1005  case 'e':
1006  /* Match day of month using alternate numeric symbols. */
1007  get_alt_number (1, 31, 2);
1008  tm->tm_mday = val;
1009  have_mday = 1;
1010  want_xday = 1;
1011  break;
1012  case 'H':
1013  /* Match hour in 24-hour clock using alternate numeric
1014  symbols. */
1015  get_alt_number (0, 23, 2);
1016  tm->tm_hour = val;
1017  have_I = 0;
1018  break;
1019  case 'I':
1020  /* Match hour in 12-hour clock using alternate numeric
1021  symbols. */
1022  get_alt_number (1, 12, 2);
1023  tm->tm_hour = val % 12;
1024  have_I = 1;
1025  break;
1026  case 'm':
1027  /* Match month using alternate numeric symbols. */
1028  get_alt_number (1, 12, 2);
1029  tm->tm_mon = val - 1;
1030  have_mon = 1;
1031  want_xday = 1;
1032  break;
1033  case 'M':
1034  /* Match minutes using alternate numeric symbols. */
1035  get_alt_number (0, 59, 2);
1036  tm->tm_min = val;
1037  break;
1038  case 'S':
1039  /* Match seconds using alternate numeric symbols.
1040  get_alt_number (0, 61, 2); */
1041  {
1042  double sval;
1043  char *end;
1044  sval = strtod(rp, &end);
1045  if( sval >= 0.0 && sval <= 61.0) {
1046  tm->tm_sec = int(sval);
1047  *psecs = sval;
1048  }
1049  rp = end;
1050  }
1051  break;
1052  case 'U':
1053  get_alt_number (0, 53, 2);
1054  week_no = val;
1055  have_uweek = 1;
1056  break;
1057  case 'W':
1058  get_alt_number (0, 53, 2);
1059  week_no = val;
1060  have_wweek = 1;
1061  break;
1062  case 'V':
1063  get_alt_number (0, 53, 2);
1064  /* XXX This cannot determine any field in TM without
1065  further information. */
1066  break;
1067  case 'w':
1068  /* Match number of weekday using alternate numeric symbols. */
1069  get_alt_number (0, 6, 1);
1070  tm->tm_wday = val;
1071  have_wday = 1;
1072  break;
1073  case 'y':
1074  /* Match year within century using alternate numeric symbols. */
1075  get_alt_number (0, 99, 2);
1076  tm->tm_year = val >= 69 ? val : val + 100;
1077  want_xday = 1;
1078  break;
1079  default:
1080  return NULL;
1081  }
1082  break;
1083  default:
1084  return NULL;
1085  }
1086  }
1087 
1088  if (have_I && is_pm)
1089  tm->tm_hour += 12;
1090 
1091  if (century != -1)
1092  {
1093  if (want_century)
1094  tm->tm_year = tm->tm_year % 100 + (century - 19) * 100;
1095  else
1096  /* Only the century, but not the year. Strange, but so be it. */
1097  tm->tm_year = (century - 19) * 100;
1098  }
1099 
1100  if (want_xday && !have_wday) {
1101  if ( !(have_mon && have_mday) && have_yday) {
1102  /* We don't have tm_mon and/or tm_mday, compute them. */
1103  int t_mon = 0;
1104  while (__mon_yday[__isleap(1900 + tm->tm_year)][t_mon] <= tm->tm_yday)
1105  t_mon++;
1106  if (!have_mon)
1107  tm->tm_mon = t_mon - 1;
1108  if (!have_mday)
1109  tm->tm_mday = (tm->tm_yday - __mon_yday[__isleap(1900 + tm->tm_year)][t_mon - 1] + 1);
1110  }
1111  day_of_the_week (tm);
1112  }
1113 
1114  if (want_xday && !have_yday)
1115  day_of_the_year (tm);
1116 
1117  if ((have_uweek || have_wweek) && have_wday) {
1118  int save_wday = tm->tm_wday;
1119  int save_mday = tm->tm_mday;
1120  int save_mon = tm->tm_mon;
1121  int w_offset = have_uweek ? 0 : 1;
1122 
1123  tm->tm_mday = 1;
1124  tm->tm_mon = 0;
1125  day_of_the_week (tm);
1126  if (have_mday)
1127  tm->tm_mday = save_mday;
1128  if (have_mon)
1129  tm->tm_mon = save_mon;
1130 
1131  if (!have_yday)
1132  tm->tm_yday = ((7 - (tm->tm_wday - w_offset)) % 7
1133  + (week_no - 1) *7
1134  + save_wday - w_offset);
1135 
1136  if (!have_mday || !have_mon)
1137  {
1138  int t_mon = 0;
1139  while (__mon_yday[__isleap(1900 + tm->tm_year)][t_mon]
1140  <= tm->tm_yday)
1141  t_mon++;
1142  if (!have_mon)
1143  tm->tm_mon = t_mon - 1;
1144  if (!have_mday)
1145  tm->tm_mday =
1146  (tm->tm_yday
1147  - __mon_yday[__isleap(1900 + tm->tm_year)][t_mon - 1] + 1);
1148  }
1149 
1150  tm->tm_wday = save_wday;
1151  }
1152 
1153  return (char *) rp;
1154 }
1155 
1156 
1157 #ifdef HAVE_LOCALE_H
1158 # include <locale.h>
1159 
1160 /* We check for a changed locale here, as setting the locale strings is
1161  on some systems slow compared to the conversions. */
1162 
1163 static void get_locale_strings(void)
1164 {
1165  int i;
1166  struct tm tm;
1167  char buff[4];
1168 
1169  tm.tm_sec = tm.tm_min = tm.tm_hour = tm.tm_mday = tm.tm_mon
1170  = tm.tm_isdst = 0;
1171  tm.tm_year = 30;
1172  for(i = 0; i < 12; i++) {
1173  tm.tm_mon = i;
1174  strftime(ab_month_name[i], 10, "%b", &tm);
1175  strftime(month_name[i], 20, "%B", &tm);
1176  }
1177  tm.tm_mon = 0;
1178  for(i = 0; i < 7; i++) {
1179  tm.tm_mday = tm.tm_yday = i+1; /* 2000-1-2 was a Sunday */
1180  tm.tm_wday = i;
1181  strftime(ab_weekday_name[i], 10, "%a", &tm);
1182  strftime(weekday_name[i], 20, "%A", &tm);
1183  }
1184  tm.tm_hour = 1;
1185  /* in locales where these are unused, they may be empty: better
1186  not to reset them then */
1187  strftime(buff, 4, "%p", &tm);
1188  if(strlen(buff)) strcpy(am_pm[0], buff);
1189  tm.tm_hour = 13;
1190  strftime(buff, 4, "%p", &tm);
1191  if(strlen(buff)) strcpy(am_pm[1], buff);
1192 }
1193 
1194 #if defined(HAVE_WCSTOD) && defined(HAVE_WCSFTIME)
1195 static void get_locale_w_strings(void)
1196 {
1197  int i;
1198  struct tm tm;
1199  wchar_t buff[4];
1200 
1201  tm.tm_sec = tm.tm_min = tm.tm_hour = tm.tm_mday = tm.tm_mon
1202  = tm.tm_isdst = 0;
1203  tm.tm_year = 30;
1204  for(i = 0; i < 12; i++) {
1205  tm.tm_mon = i;
1206  wcsftime(w_ab_month_name[i], 10, L"%b", &tm);
1207  wcsftime(w_month_name[i], 20, L"%B", &tm);
1208  }
1209  tm.tm_mon = 0;
1210  for(i = 0; i < 7; i++) {
1211  tm.tm_mday = tm.tm_yday = i+1; /* 2000-1-2 was a Sunday */
1212  tm.tm_wday = i;
1213  wcsftime(w_ab_weekday_name[i], 10, L"%a", &tm);
1214  wcsftime(w_weekday_name[i], 20, L"%A", &tm);
1215  }
1216  tm.tm_hour = 1;
1217  /* in locales where these are unused, they may be empty: better
1218  not to reset them then */
1219  wcsftime(buff, 4, L"%p", &tm);
1220  if(wcslen(buff)) wcscpy(w_am_pm[0], buff);
1221  tm.tm_hour = 13;
1222  wcsftime(buff, 4, L"%p", &tm);
1223  if(wcslen(buff)) wcscpy(w_am_pm[1], buff);
1224 }
1225 #endif
1226 #endif /* HAVE_LOCALE_H */
1227 
1228 
1229 /* We only care if the result is null or not */
1230 static char *
1231 R_strptime (const char *buf, const char *format, struct tm *tm,
1232  double *psecs, int *poffset)
1233 {
1234  enum locale_status decided;
1235  decided = raw;
1236 #if defined(HAVE_WCSTOD)
1237  if(mbcslocale) {
1238  wchar_t wbuf[1001], wfmt[1001]; size_t n;
1239 #if defined(HAVE_LOCALE_H) && defined(HAVE_WCSFTIME)
1240  get_locale_w_strings();
1241 #endif
1242  n = mbstowcs(NULL, buf, 1000);
1243  if(n > 1000) error(_("input string is too long"));
1244  n = mbstowcs(wbuf, buf, 1000);
1245  if(CXXRCONSTRUCT(int, n) == -1) error(_("invalid multibyte input string"));
1246 
1247  n = mbstowcs(NULL, format, 1000);
1248  if(n > 1000) error(_("format string is too long"));
1249  n = mbstowcs(wfmt, format, 1000);
1250  if(CXXRCONSTRUCT(int, n) == -1) error(_("invalid multibyte format string"));
1251  return (char *) w_strptime_internal (wbuf, wfmt, tm, &decided, psecs, poffset);
1252  } else
1253 #endif
1254  {
1255 #ifdef HAVE_LOCALE_H
1256  get_locale_strings();
1257 #endif
1258  return strptime_internal (buf, format, tm, &decided, psecs, poffset);
1259  }
1260 }
1261 
1262 #ifdef __cplusplus
1263 }
1264 #endif
1265 
1266 #endif /* RSTRPTIME_H */