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, 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		precision = (int)strtoul(format, &e, 10);
579		format = e - 1;
580		goto again;
581	    }
582
583	  case '%':
584	    FILL_PADDING(1);
585	    *s++ = '%';
586	    continue;
587
588	  default:
589	  unknown:
590	    i = format - sp + 1;
591	    tp = sp;
592	    precision = -1;
593	    flags = 0;
594	    padding = 0;
595	    colons = 0;
596	    break;
597	}
598	if (i) {
599	    FILL_PADDING(i);
600	    memcpy(s, tp, i);
601	    switch (flags & (BIT_OF(UPPER) | BIT_OF(LOWER))) {
602	      case BIT_OF(UPPER):
603		upcase(s, i);
604		break;
605	      case BIT_OF(LOWER):
606		downcase(s, i);
607		break;
608	    }
609	    s += i;
610	}
611    }
612    if (s >= endp) {
613	goto err;
614    }
615    if (*format == '\0') {
616	*s = '\0';
617	return (s - start);
618    }
619    return 0;
620}
621
622size_t
623date_strftime(char *s, size_t maxsize, const char *format,
624	      const struct tmx *tmx)
625{
626    return date_strftime_with_tmx(s, maxsize, format, tmx);
627}
628
629/*
630Local variables:
631c-file-style: "ruby"
632End:
633*/
634