00001
00002
00003
00004
00005
00006
00007 #include "ruby/ruby.h"
00008 #include "date_tmx.h"
00009
00010 #include <stdlib.h>
00011 #include <string.h>
00012 #include <ctype.h>
00013 #include <errno.h>
00014
00015 #if defined(HAVE_SYS_TIME_H)
00016 #include <sys/time.h>
00017 #endif
00018
00019 #undef strchr
00020
00021 #define range(low, item, hi) (item)
00022
00023 #define add(x,y) (rb_funcall((x), '+', 1, (y)))
00024 #define sub(x,y) (rb_funcall((x), '-', 1, (y)))
00025 #define mul(x,y) (rb_funcall((x), '*', 1, (y)))
00026 #define quo(x,y) (rb_funcall((x), rb_intern("quo"), 1, (y)))
00027 #define div(x,y) (rb_funcall((x), rb_intern("div"), 1, (y)))
00028 #define mod(x,y) (rb_funcall((x), '%', 1, (y)))
00029
00030 static void
00031 upcase(char *s, size_t i)
00032 {
00033 do {
00034 if (ISLOWER(*s))
00035 *s = TOUPPER(*s);
00036 } while (s++, --i);
00037 }
00038
00039 static void
00040 downcase(char *s, size_t i)
00041 {
00042 do {
00043 if (ISUPPER(*s))
00044 *s = TOLOWER(*s);
00045 } while (s++, --i);
00046 }
00047
00048
00049
00050 static size_t
00051 date_strftime_with_tmx(char *s, size_t maxsize, const char *format,
00052 const struct tmx *tmx)
00053 {
00054 char *endp = s + maxsize;
00055 char *start = s;
00056 const char *sp, *tp;
00057 auto char tbuf[100];
00058 ptrdiff_t i;
00059 int v, w;
00060 size_t colons;
00061 int precision, flags;
00062 char padding;
00063
00064 enum {LEFT, CHCASE, LOWER, UPPER, LOCALE_O, LOCALE_E, COLONS};
00065 #define BIT_OF(n) (1U<<(n))
00066
00067
00068 static const char days_l[][10] = {
00069 "Sunday", "Monday", "Tuesday", "Wednesday",
00070 "Thursday", "Friday", "Saturday",
00071 };
00072 static const char months_l[][10] = {
00073 "January", "February", "March", "April",
00074 "May", "June", "July", "August", "September",
00075 "October", "November", "December",
00076 };
00077 static const char ampm[][3] = { "AM", "PM", };
00078
00079 if (s == NULL || format == NULL || tmx == NULL || maxsize == 0)
00080 return 0;
00081
00082
00083 if (strchr(format, '%') == NULL && strlen(format) + 1 >= maxsize) {
00084 err:
00085 errno = ERANGE;
00086 return 0;
00087 }
00088
00089 for (; *format && s < endp - 1; format++) {
00090 #define FLAG_FOUND() do { \
00091 if (precision > 0 || flags & (BIT_OF(LOCALE_E) | BIT_OF(LOCALE_O) | BIT_OF(COLONS))) \
00092 goto unknown; \
00093 } while (0)
00094 #define NEEDS(n) do if (s >= endp || (n) >= endp - s - 1) goto err; while (0)
00095 #define FILL_PADDING(i) do { \
00096 if (!(flags & BIT_OF(LEFT)) && precision > (i)) { \
00097 NEEDS(precision); \
00098 memset(s, padding ? padding : ' ', precision - (i)); \
00099 s += precision - (i); \
00100 } \
00101 else { \
00102 NEEDS(i); \
00103 } \
00104 } while (0);
00105 #define FMT(def_pad, def_prec, fmt, val) \
00106 do { \
00107 int l; \
00108 if (precision <= 0) precision = (def_prec); \
00109 if (flags & BIT_OF(LEFT)) precision = 1; \
00110 l = snprintf(s, endp - s, \
00111 ((padding == '0' || (!padding && (def_pad) == '0')) ? \
00112 "%0*"fmt : "%*"fmt), \
00113 precision, (val)); \
00114 if (l < 0) goto err; \
00115 s += l; \
00116 } while (0)
00117 #define STRFTIME(fmt) \
00118 do { \
00119 i = date_strftime_with_tmx(s, endp - s, (fmt), tmx); \
00120 if (!i) return 0; \
00121 if (flags & BIT_OF(UPPER)) \
00122 upcase(s, i); \
00123 if (!(flags & BIT_OF(LEFT)) && precision > i) { \
00124 if (start + maxsize < s + precision) { \
00125 errno = ERANGE; \
00126 return 0; \
00127 } \
00128 memmove(s + precision - i, s, i); \
00129 memset(s, padding ? padding : ' ', precision - i); \
00130 s += precision; \
00131 } \
00132 else s += i; \
00133 } while (0)
00134 #define FMTV(def_pad, def_prec, fmt, val) \
00135 do { \
00136 VALUE tmp = (val); \
00137 if (FIXNUM_P(tmp)) { \
00138 FMT((def_pad), (def_prec), "l"fmt, FIX2LONG(tmp)); \
00139 } \
00140 else { \
00141 VALUE args[2], result; \
00142 size_t l; \
00143 if (precision <= 0) precision = (def_prec); \
00144 if (flags & BIT_OF(LEFT)) precision = 1; \
00145 args[0] = INT2FIX(precision); \
00146 args[1] = (val); \
00147 if (padding == '0' || (!padding && (def_pad) == '0')) \
00148 result = rb_str_format(2, args, rb_str_new2("%0*"fmt)); \
00149 else \
00150 result = rb_str_format(2, args, rb_str_new2("%*"fmt)); \
00151 l = strlcpy(s, StringValueCStr(result), endp - s); \
00152 if ((size_t)(endp - s) <= l) \
00153 goto err; \
00154 s += l; \
00155 } \
00156 } while (0)
00157
00158 if (*format != '%') {
00159 *s++ = *format;
00160 continue;
00161 }
00162 tp = tbuf;
00163 sp = format;
00164 precision = -1;
00165 flags = 0;
00166 padding = 0;
00167 colons = 0;
00168 again:
00169 switch (*++format) {
00170 case '\0':
00171 format--;
00172 goto unknown;
00173
00174 case 'A':
00175 case 'a':
00176 if (flags & BIT_OF(CHCASE)) {
00177 flags &= ~(BIT_OF(LOWER) | BIT_OF(CHCASE));
00178 flags |= BIT_OF(UPPER);
00179 }
00180 {
00181 int wday = tmx_wday;
00182 if (wday < 0 || wday > 6)
00183 i = 1, tp = "?";
00184 else {
00185 if (*format == 'A')
00186 i = strlen(tp = days_l[wday]);
00187 else
00188 i = 3, tp = days_l[wday];
00189 }
00190 }
00191 break;
00192
00193 case 'B':
00194 case 'b':
00195 case 'h':
00196 if (flags & BIT_OF(CHCASE)) {
00197 flags &= ~(BIT_OF(LOWER) | BIT_OF(CHCASE));
00198 flags |= BIT_OF(UPPER);
00199 }
00200 {
00201 int mon = tmx_mon;
00202 if (mon < 1 || mon > 12)
00203 i = 1, tp = "?";
00204 else {
00205 if (*format == 'B')
00206 i = strlen(tp = months_l[mon - 1]);
00207 else
00208 i = 3, tp = months_l[mon - 1];
00209 }
00210 }
00211 break;
00212
00213 case 'C':
00214 FMTV('0', 2, "d", div(tmx_year, INT2FIX(100)));
00215 continue;
00216
00217 case 'c':
00218 STRFTIME("%a %b %e %H:%M:%S %Y");
00219 continue;
00220
00221 case 'D':
00222 STRFTIME("%m/%d/%y");
00223 continue;
00224
00225 case 'd':
00226 case 'e':
00227 v = range(1, tmx_mday, 31);
00228 FMT((*format == 'd') ? '0' : ' ', 2, "d", v);
00229 continue;
00230
00231 case 'F':
00232 STRFTIME("%Y-%m-%d");
00233 continue;
00234
00235 case 'G':
00236 case 'Y':
00237 {
00238 VALUE year = (*format == 'G') ? tmx_cwyear : tmx_year;
00239 if (FIXNUM_P(year)) {
00240 long y = FIX2LONG(year);
00241 FMT('0', 0 <= y ? 4 : 5, "ld", y);
00242 }
00243 else {
00244 FMTV('0', 4, "d", year);
00245 }
00246 }
00247 continue;
00248
00249 case 'g':
00250 case 'y':
00251 v = NUM2INT(mod((*format == 'g') ? tmx_cwyear : tmx_year, INT2FIX(100)));
00252 FMT('0', 2, "d", v);
00253 continue;
00254
00255 case 'H':
00256 case 'k':
00257 v = range(0, tmx_hour, 23);
00258 FMT((*format == 'H') ? '0' : ' ', 2, "d", v);
00259 continue;
00260
00261 case 'I':
00262 case 'l':
00263 v = range(0, tmx_hour, 23);
00264 if (v == 0)
00265 v = 12;
00266 else if (v > 12)
00267 v -= 12;
00268 FMT((*format == 'I') ? '0' : ' ', 2, "d", v);
00269 continue;
00270
00271 case 'j':
00272 v = range(1, tmx_yday, 366);
00273 FMT('0', 3, "d", v);
00274 continue;
00275
00276 case 'L':
00277 case 'N':
00278 if (*format == 'L')
00279 w = 3;
00280 else
00281 w = 9;
00282 if (precision <= 0)
00283 precision = w;
00284 NEEDS(precision);
00285
00286 {
00287 VALUE subsec = tmx_sec_fraction;
00288 int ww;
00289 long n;
00290
00291 ww = precision;
00292 while (9 <= ww) {
00293 subsec = mul(subsec, INT2FIX(1000000000));
00294 ww -= 9;
00295 }
00296 n = 1;
00297 for (; 0 < ww; ww--)
00298 n *= 10;
00299 if (n != 1)
00300 subsec = mul(subsec, INT2FIX(n));
00301 subsec = div(subsec, INT2FIX(1));
00302
00303 if (FIXNUM_P(subsec)) {
00304 (void)snprintf(s, endp - s, "%0*ld",
00305 precision, FIX2LONG(subsec));
00306 s += precision;
00307 }
00308 else {
00309 VALUE args[2], result;
00310 args[0] = INT2FIX(precision);
00311 args[1] = subsec;
00312 result = rb_str_format(2, args, rb_str_new2("%0*d"));
00313 (void)strlcpy(s, StringValueCStr(result), endp - s);
00314 s += precision;
00315 }
00316 }
00317 continue;
00318
00319 case 'M':
00320 v = range(0, tmx_min, 59);
00321 FMT('0', 2, "d", v);
00322 continue;
00323
00324 case 'm':
00325 v = range(1, tmx_mon, 12);
00326 FMT('0', 2, "d", v);
00327 continue;
00328
00329 case 'n':
00330 FILL_PADDING(1);
00331 *s++ = '\n';
00332 continue;
00333
00334 case 't':
00335 FILL_PADDING(1);
00336 *s++ = '\t';
00337 continue;
00338
00339 case 'P':
00340 case 'p':
00341 if ((*format == 'p' && (flags & BIT_OF(CHCASE))) ||
00342 (*format == 'P' && !(flags & (BIT_OF(CHCASE) | BIT_OF(UPPER))))) {
00343 flags &= ~(BIT_OF(UPPER) | BIT_OF(CHCASE));
00344 flags |= BIT_OF(LOWER);
00345 }
00346 v = range(0, tmx_hour, 23);
00347 if (v < 12)
00348 tp = ampm[0];
00349 else
00350 tp = ampm[1];
00351 i = 2;
00352 break;
00353
00354 case 'Q':
00355 FMTV('0', 1, "d", tmx_msecs);
00356 continue;
00357
00358 case 'R':
00359 STRFTIME("%H:%M");
00360 continue;
00361
00362 case 'r':
00363 STRFTIME("%I:%M:%S %p");
00364 continue;
00365
00366 case 'S':
00367 v = range(0, tmx_sec, 59);
00368 FMT('0', 2, "d", v);
00369 continue;
00370
00371 case 's':
00372 FMTV('0', 1, "d", tmx_secs);
00373 continue;
00374
00375 case 'T':
00376 STRFTIME("%H:%M:%S");
00377 continue;
00378
00379 case 'U':
00380 case 'W':
00381 v = range(0, (*format == 'U') ? tmx_wnum0 : tmx_wnum1, 53);
00382 FMT('0', 2, "d", v);
00383 continue;
00384
00385 case 'u':
00386 v = range(1, tmx_cwday, 7);
00387 FMT('0', 1, "d", v);
00388 continue;
00389
00390 case 'V':
00391 v = range(1, tmx_cweek, 53);
00392 FMT('0', 2, "d", v);
00393 continue;
00394
00395 case 'v':
00396 STRFTIME("%e-%b-%Y");
00397 continue;
00398
00399 case 'w':
00400 v = range(0, tmx_wday, 6);
00401 FMT('0', 1, "d", v);
00402 continue;
00403
00404 case 'X':
00405 STRFTIME("%H:%M:%S");
00406 continue;
00407
00408 case 'x':
00409 STRFTIME("%m/%d/%y");
00410 continue;
00411
00412 case 'Z':
00413 if (flags & BIT_OF(CHCASE)) {
00414 flags &= ~(BIT_OF(UPPER) | BIT_OF(CHCASE));
00415 flags |= BIT_OF(LOWER);
00416 }
00417 {
00418 char *zone = tmx_zone;
00419 if (zone == NULL)
00420 tp = "";
00421 else
00422 tp = zone;
00423 i = strlen(tp);
00424 }
00425 break;
00426
00427 case 'z':
00428 {
00429 long off, aoff;
00430 int hl, hw;
00431
00432 off = tmx_offset;
00433 aoff = off;
00434 if (aoff < 0)
00435 aoff = -off;
00436
00437 if ((aoff / 3600) < 10)
00438 hl = 1;
00439 else
00440 hl = 2;
00441 hw = 2;
00442 if (flags & BIT_OF(LEFT) && hl == 1)
00443 hw = 1;
00444
00445 switch (colons) {
00446 case 0:
00447 precision = precision <= (3 + hw) ? hw : precision - 3;
00448 NEEDS(precision + 3);
00449 break;
00450
00451 case 1:
00452 precision = precision <= (4 + hw) ? hw : precision - 4;
00453 NEEDS(precision + 4);
00454 break;
00455
00456 case 2:
00457 precision = precision <= (7 + hw) ? hw : precision - 7;
00458 NEEDS(precision + 7);
00459 break;
00460
00461 case 3:
00462 {
00463 if (aoff % 3600 == 0) {
00464 precision = precision <= (1 + hw) ?
00465 hw : precision - 1;
00466 NEEDS(precision + 3);
00467 }
00468 else if (aoff % 60 == 0) {
00469 precision = precision <= (4 + hw) ?
00470 hw : precision - 4;
00471 NEEDS(precision + 4);
00472 }
00473 else {
00474 precision = precision <= (7 + hw) ?
00475 hw : precision - 7;
00476 NEEDS(precision + 7);
00477 }
00478 }
00479 break;
00480
00481 default:
00482 format--;
00483 goto unknown;
00484 }
00485 if (padding == ' ' && precision > hl) {
00486 i = snprintf(s, endp - s, "%*s", precision - hl, "");
00487 precision = hl;
00488 if (i < 0) goto err;
00489 s += i;
00490 }
00491 if (off < 0) {
00492 off = -off;
00493 *s++ = '-';
00494 } else {
00495 *s++ = '+';
00496 }
00497 i = snprintf(s, endp - s, "%.*ld", precision, off / 3600);
00498 if (i < 0) goto err;
00499 s += i;
00500 off = off % 3600;
00501 if (colons == 3 && off == 0)
00502 continue;
00503 if (1 <= colons)
00504 *s++ = ':';
00505 i = snprintf(s, endp - s, "%02d", (int)(off / 60));
00506 if (i < 0) goto err;
00507 s += i;
00508 off = off % 60;
00509 if (colons == 3 && off == 0)
00510 continue;
00511 if (2 <= colons) {
00512 *s++ = ':';
00513 i = snprintf(s, endp - s, "%02d", (int)off);
00514 if (i < 0) goto err;
00515 s += i;
00516 }
00517 }
00518 continue;
00519
00520 case '+':
00521 STRFTIME("%a %b %e %H:%M:%S %Z %Y");
00522 continue;
00523
00524 case 'E':
00525
00526 flags |= BIT_OF(LOCALE_E);
00527 if (*(format + 1) && strchr("cCxXyY", *(format + 1)))
00528 goto again;
00529 goto unknown;
00530 case 'O':
00531
00532 flags |= BIT_OF(LOCALE_O);
00533 if (*(format + 1) && strchr("deHkIlmMSuUVwWy", *(format + 1)))
00534 goto again;
00535 goto unknown;
00536
00537 case ':':
00538 flags |= BIT_OF(COLONS);
00539 {
00540 size_t l = strspn(format, ":");
00541 format += l;
00542 if (*format == 'z') {
00543 colons = l;
00544 format--;
00545 goto again;
00546 }
00547 format -= l;
00548 }
00549 goto unknown;
00550
00551 case '_':
00552 FLAG_FOUND();
00553 padding = ' ';
00554 goto again;
00555
00556 case '-':
00557 FLAG_FOUND();
00558 flags |= BIT_OF(LEFT);
00559 goto again;
00560
00561 case '^':
00562 FLAG_FOUND();
00563 flags |= BIT_OF(UPPER);
00564 goto again;
00565
00566 case '#':
00567 FLAG_FOUND();
00568 flags |= BIT_OF(CHCASE);
00569 goto again;
00570
00571 case '0':
00572 FLAG_FOUND();
00573 padding = '0';
00574 case '1': case '2': case '3': case '4':
00575 case '5': case '6': case '7': case '8': case '9':
00576 {
00577 char *e;
00578 precision = (int)strtoul(format, &e, 10);
00579 format = e - 1;
00580 goto again;
00581 }
00582
00583 case '%':
00584 FILL_PADDING(1);
00585 *s++ = '%';
00586 continue;
00587
00588 default:
00589 unknown:
00590 i = format - sp + 1;
00591 tp = sp;
00592 precision = -1;
00593 flags = 0;
00594 padding = 0;
00595 colons = 0;
00596 break;
00597 }
00598 if (i) {
00599 FILL_PADDING(i);
00600 memcpy(s, tp, i);
00601 switch (flags & (BIT_OF(UPPER) | BIT_OF(LOWER))) {
00602 case BIT_OF(UPPER):
00603 upcase(s, i);
00604 break;
00605 case BIT_OF(LOWER):
00606 downcase(s, i);
00607 break;
00608 }
00609 s += i;
00610 }
00611 }
00612 if (s >= endp) {
00613 goto err;
00614 }
00615 if (*format == '\0') {
00616 *s = '\0';
00617 return (s - start);
00618 }
00619 return 0;
00620 }
00621
00622 size_t
00623 date_strftime(char *s, size_t maxsize, const char *format,
00624 const struct tmx *tmx)
00625 {
00626 return date_strftime_with_tmx(s, maxsize, format, tmx);
00627 }
00628
00629
00630
00631
00632
00633
00634