Ruby  2.5.0dev(2017-10-22revision60238)
date_strftime.c
Go to the documentation of this file.
1 /*
2  date_strftime.c: based on a public-domain implementation of ANSI C
3  library routine strftime, which is originally written by Arnold
4  Robbins.
5  */
6 
7 #include "ruby/ruby.h"
8 #include "date_tmx.h"
9 
10 #include <stdlib.h>
11 #include <string.h>
12 #include <ctype.h>
13 #include <errno.h>
14 
15 #if defined(HAVE_SYS_TIME_H)
16 #include <sys/time.h>
17 #endif
18 
19 #undef strchr /* avoid AIX weirdness */
20 
21 #define range(low, item, hi) (item)
22 
23 #define add(x,y) (rb_funcall((x), '+', 1, (y)))
24 #define sub(x,y) (rb_funcall((x), '-', 1, (y)))
25 #define mul(x,y) (rb_funcall((x), '*', 1, (y)))
26 #define quo(x,y) (rb_funcall((x), rb_intern("quo"), 1, (y)))
27 #define div(x,y) (rb_funcall((x), rb_intern("div"), 1, (y)))
28 #define mod(x,y) (rb_funcall((x), '%', 1, (y)))
29 
30 static void
31 upcase(char *s, size_t i)
32 {
33  do {
34  if (ISLOWER(*s))
35  *s = TOUPPER(*s);
36  } while (s++, --i);
37 }
38 
39 static void
40 downcase(char *s, size_t i)
41 {
42  do {
43  if (ISUPPER(*s))
44  *s = TOLOWER(*s);
45  } while (s++, --i);
46 }
47 
48 /* strftime --- produce formatted time */
49 
50 static size_t
51 date_strftime_with_tmx(char *s, const size_t maxsize, const char *format,
52  const struct tmx *tmx)
53 {
54  char *endp = s + maxsize;
55  char *start = s;
56  const char *sp, *tp;
57  auto char tbuf[100];
58  ptrdiff_t i;
59  int v, w;
60  size_t colons;
61  int precision, flags;
62  char padding;
63  /* LOCALE_[OE] and COLONS are actually modifiers, not flags */
64  enum {LEFT, CHCASE, LOWER, UPPER, LOCALE_O, LOCALE_E, COLONS};
65 #define BIT_OF(n) (1U<<(n))
66 
67  /* various tables for locale C */
68  static const char days_l[][10] = {
69  "Sunday", "Monday", "Tuesday", "Wednesday",
70  "Thursday", "Friday", "Saturday",
71  };
72  static const char months_l[][10] = {
73  "January", "February", "March", "April",
74  "May", "June", "July", "August", "September",
75  "October", "November", "December",
76  };
77  static const char ampm[][3] = { "AM", "PM", };
78 
79  if (s == NULL || format == NULL || tmx == NULL || maxsize == 0)
80  return 0;
81 
82  /* quick check if we even need to bother */
83  if (strchr(format, '%') == NULL && strlen(format) + 1 >= maxsize) {
84  err:
85  errno = ERANGE;
86  return 0;
87  }
88 
89  for (; *format && s < endp - 1; format++) {
90 #define FLAG_FOUND() do { \
91  if (precision > 0 || flags & (BIT_OF(LOCALE_E) | BIT_OF(LOCALE_O) | BIT_OF(COLONS))) \
92  goto unknown; \
93  } while (0)
94 #define NEEDS(n) do if (s >= endp || (n) >= endp - s - 1) goto err; while (0)
95 #define FILL_PADDING(i) do { \
96  if (!(flags & BIT_OF(LEFT)) && precision > (i)) { \
97  NEEDS(precision); \
98  memset(s, padding ? padding : ' ', precision - (i)); \
99  s += precision - (i); \
100  } \
101  else { \
102  NEEDS(i); \
103  } \
104  } while (0);
105 #define FMT(def_pad, def_prec, fmt, val) \
106  do { \
107  int l; \
108  if (precision <= 0) precision = (def_prec); \
109  if (flags & BIT_OF(LEFT)) precision = 1; \
110  l = snprintf(s, endp - s, \
111  ((padding == '0' || (!padding && (def_pad) == '0')) ? \
112  "%0*"fmt : "%*"fmt), \
113  precision, (val)); \
114  if (l < 0) goto err; \
115  s += l; \
116  } while (0)
117 #define STRFTIME(fmt) \
118  do { \
119  i = date_strftime_with_tmx(s, endp - s, (fmt), tmx); \
120  if (!i) return 0; \
121  if (flags & BIT_OF(UPPER)) \
122  upcase(s, i); \
123  if (!(flags & BIT_OF(LEFT)) && precision > i) { \
124  if (start + maxsize < s + precision) { \
125  errno = ERANGE; \
126  return 0; \
127  } \
128  memmove(s + precision - i, s, i); \
129  memset(s, padding ? padding : ' ', precision - i); \
130  s += precision; \
131  } \
132  else s += i; \
133  } while (0)
134 #define FMTV(def_pad, def_prec, fmt, val) \
135  do { \
136  VALUE tmp = (val); \
137  if (FIXNUM_P(tmp)) { \
138  FMT((def_pad), (def_prec), "l"fmt, FIX2LONG(tmp)); \
139  } \
140  else { \
141  VALUE args[2], result; \
142  size_t l; \
143  if (precision <= 0) precision = (def_prec); \
144  if (flags & BIT_OF(LEFT)) precision = 1; \
145  args[0] = INT2FIX(precision); \
146  args[1] = (val); \
147  if (padding == '0' || (!padding && (def_pad) == '0')) \
148  result = rb_str_format(2, args, rb_str_new2("%0*"fmt)); \
149  else \
150  result = rb_str_format(2, args, rb_str_new2("%*"fmt)); \
151  l = strlcpy(s, StringValueCStr(result), endp - s); \
152  if ((size_t)(endp - s) <= l) \
153  goto err; \
154  s += l; \
155  } \
156  } while (0)
157 
158  if (*format != '%') {
159  *s++ = *format;
160  continue;
161  }
162  tp = tbuf;
163  sp = format;
164  precision = -1;
165  flags = 0;
166  padding = 0;
167  colons = 0;
168  again:
169  switch (*++format) {
170  case '\0':
171  format--;
172  goto unknown;
173 
174  case 'A': /* full weekday name */
175  case 'a': /* abbreviated weekday name */
176  if (flags & BIT_OF(CHCASE)) {
177  flags &= ~(BIT_OF(LOWER) | BIT_OF(CHCASE));
178  flags |= BIT_OF(UPPER);
179  }
180  {
181  int wday = tmx_wday;
182  if (wday < 0 || wday > 6)
183  i = 1, tp = "?";
184  else {
185  if (*format == 'A')
186  i = strlen(tp = days_l[wday]);
187  else
188  i = 3, tp = days_l[wday];
189  }
190  }
191  break;
192 
193  case 'B': /* full month name */
194  case 'b': /* abbreviated month name */
195  case 'h': /* same as %b */
196  if (flags & BIT_OF(CHCASE)) {
197  flags &= ~(BIT_OF(LOWER) | BIT_OF(CHCASE));
198  flags |= BIT_OF(UPPER);
199  }
200  {
201  int mon = tmx_mon;
202  if (mon < 1 || mon > 12)
203  i = 1, tp = "?";
204  else {
205  if (*format == 'B')
206  i = strlen(tp = months_l[mon - 1]);
207  else
208  i = 3, tp = months_l[mon - 1];
209  }
210  }
211  break;
212 
213  case 'C': /* century (year/100) */
214  FMTV('0', 2, "d", div(tmx_year, INT2FIX(100)));
215  continue;
216 
217  case 'c': /* appropriate date and time representation */
218  STRFTIME("%a %b %e %H:%M:%S %Y");
219  continue;
220 
221  case 'D':
222  STRFTIME("%m/%d/%y");
223  continue;
224 
225  case 'd': /* day of the month, 01 - 31 */
226  case 'e': /* day of month, blank padded */
227  v = range(1, tmx_mday, 31);
228  FMT((*format == 'd') ? '0' : ' ', 2, "d", v);
229  continue;
230 
231  case 'F':
232  STRFTIME("%Y-%m-%d");
233  continue;
234 
235  case 'G': /* year of ISO week with century */
236  case 'Y': /* year with century */
237  {
238  VALUE year = (*format == 'G') ? tmx_cwyear : tmx_year;
239  if (FIXNUM_P(year)) {
240  long y = FIX2LONG(year);
241  FMT('0', 0 <= y ? 4 : 5, "ld", y);
242  }
243  else {
244  FMTV('0', 4, "d", year);
245  }
246  }
247  continue;
248 
249  case 'g': /* year of ISO week without a century */
250  case 'y': /* year without a century */
251  v = NUM2INT(mod((*format == 'g') ? tmx_cwyear : tmx_year, INT2FIX(100)));
252  FMT('0', 2, "d", v);
253  continue;
254 
255  case 'H': /* hour, 24-hour clock, 00 - 23 */
256  case 'k': /* hour, 24-hour clock, blank pad */
257  v = range(0, tmx_hour, 23);
258  FMT((*format == 'H') ? '0' : ' ', 2, "d", v);
259  continue;
260 
261  case 'I': /* hour, 12-hour clock, 01 - 12 */
262  case 'l': /* hour, 12-hour clock, 1 - 12, blank pad */
263  v = range(0, tmx_hour, 23);
264  if (v == 0)
265  v = 12;
266  else if (v > 12)
267  v -= 12;
268  FMT((*format == 'I') ? '0' : ' ', 2, "d", v);
269  continue;
270 
271  case 'j': /* day of the year, 001 - 366 */
272  v = range(1, tmx_yday, 366);
273  FMT('0', 3, "d", v);
274  continue;
275 
276  case 'L': /* millisecond */
277  case 'N': /* nanosecond */
278  if (*format == 'L')
279  w = 3;
280  else
281  w = 9;
282  if (precision <= 0)
283  precision = w;
284  NEEDS(precision);
285 
286  {
287  VALUE subsec = tmx_sec_fraction;
288  int ww;
289  long n;
290 
291  ww = precision;
292  while (9 <= ww) {
293  subsec = mul(subsec, INT2FIX(1000000000));
294  ww -= 9;
295  }
296  n = 1;
297  for (; 0 < ww; ww--)
298  n *= 10;
299  if (n != 1)
300  subsec = mul(subsec, INT2FIX(n));
301  subsec = div(subsec, INT2FIX(1));
302 
303  if (FIXNUM_P(subsec)) {
304  (void)snprintf(s, endp - s, "%0*ld",
305  precision, FIX2LONG(subsec));
306  s += precision;
307  }
308  else {
309  VALUE args[2], result;
310  args[0] = INT2FIX(precision);
311  args[1] = subsec;
312  result = rb_str_format(2, args, rb_str_new2("%0*d"));
313  (void)strlcpy(s, StringValueCStr(result), endp - s);
314  s += precision;
315  }
316  }
317  continue;
318 
319  case 'M': /* minute, 00 - 59 */
320  v = range(0, tmx_min, 59);
321  FMT('0', 2, "d", v);
322  continue;
323 
324  case 'm': /* month, 01 - 12 */
325  v = range(1, tmx_mon, 12);
326  FMT('0', 2, "d", v);
327  continue;
328 
329  case 'n': /* same as \n */
330  FILL_PADDING(1);
331  *s++ = '\n';
332  continue;
333 
334  case 't': /* same as \t */
335  FILL_PADDING(1);
336  *s++ = '\t';
337  continue;
338 
339  case 'P': /* am or pm based on 12-hour clock */
340  case 'p': /* AM or PM based on 12-hour clock */
341  if ((*format == 'p' && (flags & BIT_OF(CHCASE))) ||
342  (*format == 'P' && !(flags & (BIT_OF(CHCASE) | BIT_OF(UPPER))))) {
343  flags &= ~(BIT_OF(UPPER) | BIT_OF(CHCASE));
344  flags |= BIT_OF(LOWER);
345  }
346  v = range(0, tmx_hour, 23);
347  if (v < 12)
348  tp = ampm[0];
349  else
350  tp = ampm[1];
351  i = 2;
352  break;
353 
354  case 'Q': /* milliseconds since Unix epoch */
355  FMTV('0', 1, "d", tmx_msecs);
356  continue;
357 
358  case 'R':
359  STRFTIME("%H:%M");
360  continue;
361 
362  case 'r':
363  STRFTIME("%I:%M:%S %p");
364  continue;
365 
366  case 'S': /* second, 00 - 59 */
367  v = range(0, tmx_sec, 59);
368  FMT('0', 2, "d", v);
369  continue;
370 
371  case 's': /* seconds since Unix epoch */
372  FMTV('0', 1, "d", tmx_secs);
373  continue;
374 
375  case 'T':
376  STRFTIME("%H:%M:%S");
377  continue;
378 
379  case 'U': /* week of year, Sunday is first day of week */
380  case 'W': /* week of year, Monday is first day of week */
381  v = range(0, (*format == 'U') ? tmx_wnum0 : tmx_wnum1, 53);
382  FMT('0', 2, "d", v);
383  continue;
384 
385  case 'u': /* weekday, Monday == 1, 1 - 7 */
386  v = range(1, tmx_cwday, 7);
387  FMT('0', 1, "d", v);
388  continue;
389 
390  case 'V': /* week of year according ISO 8601 */
391  v = range(1, tmx_cweek, 53);
392  FMT('0', 2, "d", v);
393  continue;
394 
395  case 'v':
396  STRFTIME("%e-%b-%Y");
397  continue;
398 
399  case 'w': /* weekday, Sunday == 0, 0 - 6 */
400  v = range(0, tmx_wday, 6);
401  FMT('0', 1, "d", v);
402  continue;
403 
404  case 'X': /* appropriate time representation */
405  STRFTIME("%H:%M:%S");
406  continue;
407 
408  case 'x': /* appropriate date representation */
409  STRFTIME("%m/%d/%y");
410  continue;
411 
412  case 'Z': /* time zone name or abbreviation */
413  if (flags & BIT_OF(CHCASE)) {
414  flags &= ~(BIT_OF(UPPER) | BIT_OF(CHCASE));
415  flags |= BIT_OF(LOWER);
416  }
417  {
418  char *zone = tmx_zone;
419  if (zone == NULL)
420  tp = "";
421  else
422  tp = zone;
423  i = strlen(tp);
424  }
425  break;
426 
427  case 'z': /* offset from UTC */
428  {
429  long off, aoff;
430  int hl, hw;
431 
432  off = tmx_offset;
433  aoff = off;
434  if (aoff < 0)
435  aoff = -off;
436 
437  if ((aoff / 3600) < 10)
438  hl = 1;
439  else
440  hl = 2;
441  hw = 2;
442  if (flags & BIT_OF(LEFT) && hl == 1)
443  hw = 1;
444 
445  switch (colons) {
446  case 0: /* %z -> +hhmm */
447  precision = precision <= (3 + hw) ? hw : precision - 3;
448  NEEDS(precision + 3);
449  break;
450 
451  case 1: /* %:z -> +hh:mm */
452  precision = precision <= (4 + hw) ? hw : precision - 4;
453  NEEDS(precision + 4);
454  break;
455 
456  case 2: /* %::z -> +hh:mm:ss */
457  precision = precision <= (7 + hw) ? hw : precision - 7;
458  NEEDS(precision + 7);
459  break;
460 
461  case 3: /* %:::z -> +hh[:mm[:ss]] */
462  {
463  if (aoff % 3600 == 0) {
464  precision = precision <= (1 + hw) ?
465  hw : precision - 1;
466  NEEDS(precision + 3);
467  }
468  else if (aoff % 60 == 0) {
469  precision = precision <= (4 + hw) ?
470  hw : precision - 4;
471  NEEDS(precision + 4);
472  }
473  else {
474  precision = precision <= (7 + hw) ?
475  hw : precision - 7;
476  NEEDS(precision + 7);
477  }
478  }
479  break;
480 
481  default:
482  format--;
483  goto unknown;
484  }
485  if (padding == ' ' && precision > hl) {
486  i = snprintf(s, endp - s, "%*s", precision - hl, "");
487  precision = hl;
488  if (i < 0) goto err;
489  s += i;
490  }
491  if (off < 0) {
492  off = -off;
493  *s++ = '-';
494  } else {
495  *s++ = '+';
496  }
497  i = snprintf(s, endp - s, "%.*ld", precision, off / 3600);
498  if (i < 0) goto err;
499  s += i;
500  off = off % 3600;
501  if (colons == 3 && off == 0)
502  continue;
503  if (1 <= colons)
504  *s++ = ':';
505  i = snprintf(s, endp - s, "%02d", (int)(off / 60));
506  if (i < 0) goto err;
507  s += i;
508  off = off % 60;
509  if (colons == 3 && off == 0)
510  continue;
511  if (2 <= colons) {
512  *s++ = ':';
513  i = snprintf(s, endp - s, "%02d", (int)off);
514  if (i < 0) goto err;
515  s += i;
516  }
517  }
518  continue;
519 
520  case '+':
521  STRFTIME("%a %b %e %H:%M:%S %Z %Y");
522  continue;
523 
524  case 'E':
525  /* POSIX locale extensions, ignored for now */
526  flags |= BIT_OF(LOCALE_E);
527  if (*(format + 1) && strchr("cCxXyY", *(format + 1)))
528  goto again;
529  goto unknown;
530  case 'O':
531  /* POSIX locale extensions, ignored for now */
532  flags |= BIT_OF(LOCALE_O);
533  if (*(format + 1) && strchr("deHkIlmMSuUVwWy", *(format + 1)))
534  goto again;
535  goto unknown;
536 
537  case ':':
538  flags |= BIT_OF(COLONS);
539  {
540  size_t l = strspn(format, ":");
541  format += l;
542  if (*format == 'z') {
543  colons = l;
544  format--;
545  goto again;
546  }
547  format -= l;
548  }
549  goto unknown;
550 
551  case '_':
552  FLAG_FOUND();
553  padding = ' ';
554  goto again;
555 
556  case '-':
557  FLAG_FOUND();
558  flags |= BIT_OF(LEFT);
559  goto again;
560 
561  case '^':
562  FLAG_FOUND();
563  flags |= BIT_OF(UPPER);
564  goto again;
565 
566  case '#':
567  FLAG_FOUND();
568  flags |= BIT_OF(CHCASE);
569  goto again;
570 
571  case '0':
572  FLAG_FOUND();
573  padding = '0';
574  case '1': case '2': case '3': case '4':
575  case '5': case '6': case '7': case '8': case '9':
576  {
577  char *e;
578  unsigned long prec = strtoul(format, &e, 10);
579  if (prec > INT_MAX || prec > maxsize) {
580  errno = ERANGE;
581  return 0;
582  }
583  precision = (int)prec;
584  format = e - 1;
585  goto again;
586  }
587 
588  case '%':
589  FILL_PADDING(1);
590  *s++ = '%';
591  continue;
592 
593  default:
594  unknown:
595  i = format - sp + 1;
596  tp = sp;
597  precision = -1;
598  flags = 0;
599  padding = 0;
600  colons = 0;
601  break;
602  }
603  if (i) {
604  FILL_PADDING(i);
605  memcpy(s, tp, i);
606  switch (flags & (BIT_OF(UPPER) | BIT_OF(LOWER))) {
607  case BIT_OF(UPPER):
608  upcase(s, i);
609  break;
610  case BIT_OF(LOWER):
611  downcase(s, i);
612  break;
613  }
614  s += i;
615  }
616  }
617  if (s >= endp) {
618  goto err;
619  }
620  if (*format == '\0') {
621  *s = '\0';
622  return (s - start);
623  }
624  return 0;
625 }
626 
627 size_t
628 date_strftime(char *s, size_t maxsize, const char *format,
629  const struct tmx *tmx)
630 {
631  return date_strftime_with_tmx(s, maxsize, format, tmx);
632 }
633 
634 /*
635 Local variables:
636 c-file-style: "ruby"
637 End:
638 */
#define tmx_cwday
Definition: date_tmx.h:37
Definition: date_tmx.h:24
size_t strlen(const char *)
#define tmx_secs
Definition: date_tmx.h:45
#define FMT(def_pad, def_prec, fmt, val)
#define tmx_wnum1
Definition: date_tmx.h:39
#define NUM2INT(x)
Definition: ruby.h:684
#define tmx_cwyear
Definition: date_tmx.h:35
#define tmx_hour
Definition: date_tmx.h:41
Definition: strftime.c:160
#define FIXNUM_P(f)
Definition: ruby.h:365
size_t date_strftime(char *s, size_t maxsize, const char *format, const struct tmx *tmx)
#define tmx_zone
Definition: date_tmx.h:48
#define div(x, y)
Definition: date_strftime.c:27
#define NEEDS(n)
#define snprintf
Definition: subst.h:6
#define TOUPPER(c)
Definition: ruby.h:2153
#define range(low, item, hi)
Definition: date_strftime.c:21
#define ISUPPER(c)
Definition: ruby.h:2146
#define rb_str_new2
Definition: intern.h:835
int err
Definition: win32.c:135
#define ISLOWER(c)
Definition: ruby.h:2147
#define tmx_sec
Definition: date_tmx.h:43
int errno
VALUE rb_str_format(int, const VALUE *, VALUE)
Definition: sprintf.c:464
#define FMTV(def_pad, def_prec, fmt, val)
#define STRFTIME(fmt)
#define tmx_min
Definition: date_tmx.h:42
#define tmx_cweek
Definition: date_tmx.h:36
unsigned long VALUE
Definition: ruby.h:85
char * strchr(char *, char)
#define tmx_year
Definition: date_tmx.h:31
RUBY_EXTERN size_t strlcpy(char *, const char *, size_t)
Definition: strlcpy.c:29
#define tmx_mon
Definition: date_tmx.h:33
#define tmx_msecs
Definition: date_tmx.h:46
#define StringValueCStr(v)
Definition: ruby.h:571
#define tmx_sec_fraction
Definition: date_tmx.h:44
#define tmx_mday
Definition: date_tmx.h:34
#define tmx_offset
Definition: date_tmx.h:47
#define INT2FIX(i)
Definition: ruby.h:232
#define mul(x, y)
Definition: date_strftime.c:25
#define FLAG_FOUND()
#define FILL_PADDING(i)
Definition: zonetab.h:34
#define TOLOWER(c)
Definition: ruby.h:2154
#define tmx_wnum0
Definition: date_tmx.h:38
#define mod(x, y)
Definition: date_strftime.c:28
#define tmx_yday
Definition: date_tmx.h:32
#define NULL
Definition: _sdbm.c:102
#define FIX2LONG(x)
Definition: ruby.h:363
#define BIT_OF(n)
#define tmx_wday
Definition: date_tmx.h:40