Ruby: ext/date/date_strftime.c Source File

Ruby 3.0.5p211 (2022-11-24 revision ba5cf0f7c52d4d35cc6a173c89eda98ceffa2dcf)

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
30static void
31upcase(char *s, size_t i)
32{
33 do {
34 if (ISLOWER(*s))
35 *s = TOUPPER(*s);
36 } while (s++, --i);
37}
38
39static void
40downcase(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
50static size_t
51date_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
627size_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/*
635Local variables:
636c-file-style: "ruby"
637End:
638*/
ctype.h
Our own, locale independent, character handling routines.
ISUPPER
#define ISUPPER
Definition: ctype.h:39
TOUPPER
#define TOUPPER
Definition: ctype.h:50
ISLOWER
#define ISLOWER
Definition: ctype.h:40
TOLOWER
#define TOLOWER
Definition: ctype.h:51
STRFTIME
#define STRFTIME(fmt)
NEEDS
#define NEEDS(n)
FILL_PADDING
#define FILL_PADDING(i)
date_strftime
size_t date_strftime(char *s, size_t maxsize, const char *format, const struct tmx *tmx)
Definition: date_strftime.c:628
mul
#define mul(x, y)
Definition: date_strftime.c:25
BIT_OF
#define BIT_OF(n)
FMTV
#define FMTV(def_pad, def_prec, fmt, val)
mod
#define mod(x, y)
Definition: date_strftime.c:28
FLAG_FOUND
#define FLAG_FOUND()
div
#define div(x, y)
Definition: date_strftime.c:27
range
#define range(low, item, hi)
Definition: date_strftime.c:21
FMT
#define FMT(def_pad, def_prec, fmt, val)
date_tmx.h
tmx_mday
#define tmx_mday
Definition: date_tmx.h:34
tmx_offset
#define tmx_offset
Definition: date_tmx.h:47
tmx_zone
#define tmx_zone
Definition: date_tmx.h:48
tmx_msecs
#define tmx_msecs
Definition: date_tmx.h:46
tmx_cwday
#define tmx_cwday
Definition: date_tmx.h:37
tmx_min
#define tmx_min
Definition: date_tmx.h:42
tmx_sec
#define tmx_sec
Definition: date_tmx.h:43
tmx_wday
#define tmx_wday
Definition: date_tmx.h:40
tmx_cweek
#define tmx_cweek
Definition: date_tmx.h:36
tmx_wnum0
#define tmx_wnum0
Definition: date_tmx.h:38
tmx_year
#define tmx_year
Definition: date_tmx.h:31
tmx_mon
#define tmx_mon
Definition: date_tmx.h:33
tmx_cwyear
#define tmx_cwyear
Definition: date_tmx.h:35
tmx_hour
#define tmx_hour
Definition: date_tmx.h:41
tmx_secs
#define tmx_secs
Definition: date_tmx.h:45
tmx_sec_fraction
#define tmx_sec_fraction
Definition: date_tmx.h:44
tmx_yday
#define tmx_yday
Definition: date_tmx.h:32
tmx_wnum1
#define tmx_wnum1
Definition: date_tmx.h:39
strchr
char * strchr(char *, char)
memcpy
#define memcpy(d, s, n)
Definition: ffi_common.h:55
rb_str_new2
#define rb_str_new2
Definition: string.h:276
strlcpy
size_t strlcpy(char *, const char *, size_t)
Definition: strlcpy.c:29
NUM2INT
#define NUM2INT
Definition: int.h:44
int
typedef int(ZCALLBACK *close_file_func) OF((voidpf opaque
INT2FIX
#define INT2FIX
Definition: long.h:48
FIX2LONG
#define FIX2LONG
Definition: long.h:46
NULL
#define NULL
Definition: regenc.h:69
StringValueCStr
#define StringValueCStr(v)
Definition: rstring.h:52
ruby.h
FIXNUM_P
#define FIXNUM_P
Definition: special_consts.h:44
rb_str_format
VALUE rb_str_format(int, const VALUE *, VALUE)
Definition: sprintf.c:214
strlen
size_t strlen(const char *)
LOWER
@ LOWER
Definition: strftime.c:165
LEFT
@ LEFT
Definition: strftime.c:165
UPPER
@ UPPER
Definition: strftime.c:165
CHCASE
@ CHCASE
Definition: strftime.c:165
tmx
Definition: date_tmx.h:24
zone
Definition: zonetab.h:35
snprintf
#define snprintf
Definition: subst.h:14
VALUE
unsigned long VALUE
Definition: value.h:38
err
int err
Definition: win32.c:142