Ruby  2.0.0p247(2013-06-27revision41674)
ext/date/date_strftime.c
Go to the documentation of this file.
00001 /*
00002   date_strftime.c: based on a public-domain implementation of ANSI C
00003   library routine strftime, which is originally written by Arnold
00004   Robbins.
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   /* avoid AIX weirdness */
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 /* strftime --- produce formatted time */
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     /* LOCALE_[OE] and COLONS are actually modifiers, not flags */
00064     enum {LEFT, CHCASE, LOWER, UPPER, LOCALE_O, LOCALE_E, COLONS};
00065 #define BIT_OF(n) (1U<<(n))
00066 
00067     /* various tables for locale C */
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     /* quick check if we even need to bother */
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':     /* full weekday name */
00175           case 'a':     /* abbreviated weekday name */
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':     /* full month name */
00194           case 'b':     /* abbreviated month name */
00195           case 'h':     /* same as %b */
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':     /* century (year/100) */
00214             FMTV('0', 2, "d", div(tmx_year, INT2FIX(100)));
00215             continue;
00216 
00217           case 'c':     /* appropriate date and time representation */
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':     /* day of the month, 01 - 31 */
00226           case 'e':     /* day of month, blank padded */
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':     /* year of ISO week with century */
00236           case 'Y':     /* year with century */
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':     /* year of ISO week without a century */
00250           case 'y':     /* year without a century */
00251             v = NUM2INT(mod((*format == 'g') ? tmx_cwyear : tmx_year, INT2FIX(100)));
00252             FMT('0', 2, "d", v);
00253             continue;
00254 
00255           case 'H':     /* hour, 24-hour clock, 00 - 23 */
00256           case 'k':     /* hour, 24-hour clock, blank pad */
00257             v = range(0, tmx_hour, 23);
00258             FMT((*format == 'H') ? '0' : ' ', 2, "d", v);
00259             continue;
00260 
00261           case 'I':     /* hour, 12-hour clock, 01 - 12 */
00262           case 'l':     /* hour, 12-hour clock, 1 - 12, blank pad */
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':     /* day of the year, 001 - 366 */
00272             v = range(1, tmx_yday, 366);
00273             FMT('0', 3, "d", v);
00274             continue;
00275 
00276           case 'L':     /* millisecond */
00277           case 'N':     /* nanosecond */
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':     /* minute, 00 - 59 */
00320             v = range(0, tmx_min, 59);
00321             FMT('0', 2, "d", v);
00322             continue;
00323 
00324           case 'm':     /* month, 01 - 12 */
00325             v = range(1, tmx_mon, 12);
00326             FMT('0', 2, "d", v);
00327             continue;
00328 
00329           case 'n':     /* same as \n */
00330             FILL_PADDING(1);
00331             *s++ = '\n';
00332             continue;
00333 
00334           case 't':     /* same as \t */
00335             FILL_PADDING(1);
00336             *s++ = '\t';
00337             continue;
00338 
00339           case 'P':     /* am or pm based on 12-hour clock */
00340           case 'p':     /* AM or PM based on 12-hour clock */
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':     /* milliseconds since Unix epoch */
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':     /* second, 00 - 59 */
00367             v = range(0, tmx_sec, 59);
00368             FMT('0', 2, "d", v);
00369             continue;
00370 
00371           case 's':     /* seconds since Unix epoch */
00372             FMTV('0', 1, "d", tmx_secs);
00373             continue;
00374 
00375           case 'T':
00376             STRFTIME("%H:%M:%S");
00377             continue;
00378 
00379           case 'U':     /* week of year, Sunday is first day of week */
00380           case 'W':     /* week of year, Monday is first day of week */
00381             v = range(0, (*format == 'U') ? tmx_wnum0 : tmx_wnum1, 53);
00382             FMT('0', 2, "d", v);
00383             continue;
00384 
00385           case 'u':     /* weekday, Monday == 1, 1 - 7 */
00386             v = range(1, tmx_cwday, 7);
00387             FMT('0', 1, "d", v);
00388             continue;
00389 
00390           case 'V':     /* week of year according ISO 8601 */
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':     /* weekday, Sunday == 0, 0 - 6 */
00400             v = range(0, tmx_wday, 6);
00401             FMT('0', 1, "d", v);
00402             continue;
00403 
00404           case 'X':     /* appropriate time representation */
00405             STRFTIME("%H:%M:%S");
00406             continue;
00407 
00408           case 'x':     /* appropriate date representation */
00409             STRFTIME("%m/%d/%y");
00410             continue;
00411 
00412           case 'Z':     /* time zone name or abbreviation */
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':     /* offset from UTC */
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: /* %z -> +hhmm */
00447                     precision = precision <= (3 + hw) ? hw : precision - 3;
00448                     NEEDS(precision + 3);
00449                     break;
00450 
00451                   case 1: /* %:z -> +hh:mm */
00452                     precision = precision <= (4 + hw) ? hw : precision - 4;
00453                     NEEDS(precision + 4);
00454                     break;
00455 
00456                   case 2: /* %::z -> +hh:mm:ss */
00457                     precision = precision <= (7 + hw) ? hw : precision - 7;
00458                     NEEDS(precision + 7);
00459                     break;
00460 
00461                   case 3: /* %:::z -> +hh[:mm[:ss]] */
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             /* POSIX locale extensions, ignored for now */
00526             flags |= BIT_OF(LOCALE_E);
00527             if (*(format + 1) && strchr("cCxXyY", *(format + 1)))
00528                 goto again;
00529             goto unknown;
00530           case 'O':
00531             /* POSIX locale extensions, ignored for now */
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 Local variables:
00631 c-file-style: "ruby"
00632 End:
00633 */
00634