summaryrefslogtreecommitdiff
path: root/strftime.c
diff options
context:
space:
mode:
Diffstat (limited to 'strftime.c')
-rw-r--r--strftime.c331
1 files changed, 227 insertions, 104 deletions
diff --git a/strftime.c b/strftime.c
index b05653ffd5..9be4bb9d07 100644
--- a/strftime.c
+++ b/strftime.c
@@ -47,9 +47,7 @@
* January 1996
*/
-#include "ruby/ruby.h"
-#include "ruby/encoding.h"
-#include "timev.h"
+#include "ruby/internal/config.h"
#ifndef GAWK
#include <stdio.h>
@@ -61,12 +59,21 @@
#endif
#if defined(TM_IN_SYS_TIME) || !defined(GAWK)
#include <sys/types.h>
-#if HAVE_SYS_TIME_H
+#ifdef HAVE_SYS_TIME_H
#include <sys/time.h>
#endif
#endif
#include <math.h>
+#include "internal.h"
+#include "internal/encoding.h"
+#include "internal/string.h"
+#include "internal/vm.h"
+#include "ruby/encoding.h"
+#include "ruby/ruby.h"
+#include "ruby/util.h"
+#include "timev.h"
+
/* defaults: season to taste */
#define SYSV_EXT 1 /* stuff in System V ascftime routine */
#define SUNOS_EXT 1 /* stuff in SunOS strftime routine */
@@ -156,16 +163,85 @@ max(int a, int b)
/* strftime --- produce formatted time */
+enum {LEFT, CHCASE, LOWER, UPPER};
+#define BIT_OF(n) (1U<<(n))
+
+static char *
+resize_buffer(VALUE ftime, char *s, const char **start, const char **endp,
+ ptrdiff_t n, size_t maxsize)
+{
+ size_t len = s - *start;
+ size_t need = len + n * 2;
+ size_t nlen = rb_str_capacity(ftime);
+ while (nlen < need) nlen <<= 1;
+
+ if (nlen < len || nlen > maxsize) {
+ return 0;
+ }
+ rb_str_set_len(ftime, len);
+ rb_str_modify_expand(ftime, nlen-len);
+ s = RSTRING_PTR(ftime);
+ *endp = s + nlen;
+ *start = s;
+ return s += len;
+}
+
+static void
+buffer_size_check(const char *s,
+ const char *format_end, size_t format_len,
+ rb_encoding *enc)
+{
+ if (!s) {
+ const char *format = format_end-format_len;
+ VALUE fmt = rb_enc_str_new(format, format_len, enc);
+ rb_syserr_fail_str(ERANGE, fmt);
+ }
+}
+
+static char *
+case_conv(char *s, ptrdiff_t i, int flags)
+{
+ switch (flags & (BIT_OF(UPPER)|BIT_OF(LOWER))) {
+ case BIT_OF(UPPER):
+ do {
+ if (ISLOWER(*s)) *s = TOUPPER(*s);
+ } while (s++, --i);
+ break;
+ case BIT_OF(LOWER):
+ do {
+ if (ISUPPER(*s)) *s = TOLOWER(*s);
+ } while (s++, --i);
+ break;
+ default:
+ s += i;
+ break;
+ }
+ return s;
+}
+
+static VALUE
+format_value(VALUE val, int base)
+{
+ if (!RB_BIGNUM_TYPE_P(val))
+ val = rb_Integer(val);
+ return rb_big2str(val, base);
+}
+
/*
* enc is the encoding of the format. It is used as the encoding of resulted
* string, but the name of the month and weekday are always US-ASCII. So it
* is only used for the timezone name on Windows.
*/
-static size_t
-rb_strftime_with_timespec(char *s, size_t maxsize, const char *format, rb_encoding *enc, const struct vtm *vtm, VALUE timev, struct timespec *ts, int gmt)
+static VALUE
+rb_strftime_with_timespec(VALUE ftime, const char *format, size_t format_len,
+ rb_encoding *enc, VALUE time, const struct vtm *vtm,
+ VALUE timev, struct timespec *ts, int gmt, size_t maxsize)
{
- const char *const endp = s + maxsize;
- const char *const start = s;
+ size_t len = RSTRING_LEN(ftime);
+ char *s = RSTRING_PTR(ftime);
+ const char *start = s;
+ const char *endp = start + rb_str_capacity(ftime);
+ const char *const format_end = format + format_len;
const char *sp, *tp;
#define TBUFSIZE 100
auto char tbuf[TBUFSIZE];
@@ -175,11 +251,10 @@ rb_strftime_with_timespec(char *s, size_t maxsize, const char *format, rb_encodi
long y;
int precision, flags, colons;
char padding;
- enum {LEFT, CHCASE, LOWER, UPPER};
-#define BIT_OF(n) (1U<<(n))
#ifdef MAILHEADER_EXT
int sign;
#endif
+ VALUE zone = Qnil;
/* various tables, useful in North America */
static const char days_l[][10] = {
@@ -193,27 +268,29 @@ rb_strftime_with_timespec(char *s, size_t maxsize, const char *format, rb_encodi
};
static const char ampm[][3] = { "AM", "PM", };
- if (s == NULL || format == NULL || vtm == NULL || maxsize == 0)
- return 0;
-
- /* quick check if we even need to bother */
- if (strchr(format, '%') == NULL && strlen(format) + 1 >= maxsize) {
- err:
- errno = ERANGE;
- return 0;
+ if (format == NULL || format_len == 0 || vtm == NULL) {
+ goto err;
}
- if (enc && (enc == rb_usascii_encoding() ||
- enc == rb_ascii8bit_encoding() || enc == rb_locale_encoding())) {
- enc = NULL;
+ if (enc &&
+ (rb_is_usascii_enc(enc) ||
+ rb_is_ascii8bit_enc(enc) ||
+ rb_is_locale_enc(enc))) {
+ enc = NULL;
}
- for (; *format && s < endp - 1; format++) {
+ s += len;
+ for (; format < format_end; format++) {
#define FLAG_FOUND() do { \
if (precision > 0) \
goto unknown; \
} while (0)
-#define NEEDS(n) do if (s >= endp || (n) >= endp - s - 1) goto err; while (0)
+#define NEEDS(n) do { \
+ if (s >= endp || (n) >= endp - s - 1) { \
+ s = resize_buffer(ftime, s, &start, &endp, (n), maxsize); \
+ buffer_size_check(s, format_end, format_len, enc); \
+ } \
+ } while (0)
#define FILL_PADDING(i) do { \
if (!(flags & BIT_OF(LEFT)) && precision > (i)) { \
NEEDS(precision); \
@@ -224,27 +301,45 @@ rb_strftime_with_timespec(char *s, size_t maxsize, const char *format, rb_encodi
NEEDS(i); \
} \
} while (0);
+#define FMT_PADDING(fmt, def_pad) \
+ (&"%*"fmt"\0""%0*"fmt[\
+ (padding == '0' || (!padding && (def_pad) == '0')) ? \
+ rb_strlen_lit("%*"fmt)+1 : 0])
+#define FMT_PRECISION(def_prec) \
+ ((flags & BIT_OF(LEFT)) ? (1) : \
+ (precision <= 0) ? (def_prec) : (precision))
#define FMT(def_pad, def_prec, fmt, val) \
do { \
- int l; \
- if (precision <= 0) precision = (def_prec); \
- if (flags & BIT_OF(LEFT)) precision = 1; \
- l = snprintf(s, endp - s, \
- ((padding == '0' || (!padding && (def_pad) == '0')) ? "%0*"fmt : "%*"fmt), \
- precision, (val)); \
- if (l < 0) goto err; \
- s += l; \
+ precision = FMT_PRECISION(def_prec); \
+ len = s - start; \
+ NEEDS(precision); \
+ rb_str_set_len(ftime, len); \
+ rb_str_catf(ftime, FMT_PADDING(fmt, def_pad), \
+ precision, (val)); \
+ RSTRING_GETMEM(ftime, s, len); \
+ endp = (start = s) + rb_str_capacity(ftime); \
+ s += len; \
} while (0)
#define STRFTIME(fmt) \
do { \
- i = rb_strftime_with_timespec(s, endp - s, (fmt), enc, vtm, timev, ts, gmt); \
- if (!i) return 0; \
+ len = s - start; \
+ rb_str_set_len(ftime, len); \
+ if (!rb_strftime_with_timespec(ftime, (fmt), rb_strlen_lit(fmt), \
+ enc, time, vtm, timev, ts, gmt, maxsize)) \
+ return 0; \
+ s = RSTRING_PTR(ftime); \
+ i = RSTRING_LEN(ftime) - len; \
+ endp = (start = s) + rb_str_capacity(ftime); \
+ s += len; \
+ if (i > 0) case_conv(s, i, flags); \
if (precision > i) {\
+ s += i; \
NEEDS(precision); \
+ s -= i; \
memmove(s + precision - i, s, i);\
memset(s, padding ? padding : ' ', precision - i); \
s += precision; \
- }\
+ } \
else s += i; \
} while (0)
#define FMTV(def_pad, def_prec, fmt, val) \
@@ -254,27 +349,30 @@ rb_strftime_with_timespec(char *s, size_t maxsize, const char *format, rb_encodi
FMT((def_pad), (def_prec), "l"fmt, FIX2LONG(tmp)); \
} \
else { \
- VALUE args[2], result; \
- size_t l; \
- if (precision <= 0) precision = (def_prec); \
- if (flags & BIT_OF(LEFT)) precision = 1; \
- args[0] = INT2FIX(precision); \
- args[1] = (val); \
- if (padding == '0' || (!padding && (def_pad) == '0')) \
- result = rb_str_format(2, args, rb_str_new2("%0*"fmt)); \
- else \
- result = rb_str_format(2, args, rb_str_new2("%*"fmt)); \
- l = strlcpy(s, StringValueCStr(result), endp-s); \
- if ((size_t)(endp-s) <= l) \
- goto err; \
- s += l; \
+ const int base = ((fmt[0] == 'x') ? 16 : \
+ (fmt[0] == 'o') ? 8 : \
+ 10); \
+ precision = FMT_PRECISION(def_prec); \
+ if (!padding) padding = (def_pad); \
+ tmp = format_value(tmp, base); \
+ i = RSTRING_LEN(tmp); \
+ FILL_PADDING(i); \
+ rb_str_set_len(ftime, s-start); \
+ rb_str_append(ftime, tmp); \
+ RSTRING_GETMEM(ftime, s, len); \
+ endp = (start = s) + rb_str_capacity(ftime); \
+ s += len; \
} \
} while (0)
- if (*format != '%') {
- *s++ = *format;
- continue;
- }
+ tp = memchr(format, '%', format_end - format);
+ if (!tp) tp = format_end;
+ NEEDS(tp - format);
+ memcpy(s, format, tp - format);
+ s += tp - format;
+ format = tp;
+ if (format == format_end) break;
+
tp = tbuf;
sp = format;
precision = -1;
@@ -282,11 +380,8 @@ rb_strftime_with_timespec(char *s, size_t maxsize, const char *format, rb_encodi
padding = 0;
colons = 0;
again:
- switch (*++format) {
- case '\0':
- format--;
- goto unknown;
-
+ if (++format >= format_end) goto unknown;
+ switch (*format) {
case '%':
FILL_PADDING(1);
*s++ = '%';
@@ -297,7 +392,7 @@ rb_strftime_with_timespec(char *s, size_t maxsize, const char *format, rb_encodi
flags &= ~(BIT_OF(LOWER)|BIT_OF(CHCASE));
flags |= BIT_OF(UPPER);
}
- if (vtm->wday < 0 || vtm->wday > 6)
+ if (vtm->wday > 6)
i = 1, tp = "?";
else
i = 3, tp = days_l[vtm->wday];
@@ -308,7 +403,7 @@ rb_strftime_with_timespec(char *s, size_t maxsize, const char *format, rb_encodi
flags &= ~(BIT_OF(LOWER)|BIT_OF(CHCASE));
flags |= BIT_OF(UPPER);
}
- if (vtm->wday < 0 || vtm->wday > 6)
+ if (vtm->wday > 6)
i = 1, tp = "?";
else
i = strlen(tp = days_l[vtm->wday]);
@@ -363,7 +458,8 @@ rb_strftime_with_timespec(char *s, size_t maxsize, const char *format, rb_encodi
continue;
case 'j': /* day of the year, 001 - 366 */
- FMT('0', 3, "d", vtm->yday);
+ i = range(1, vtm->yday, 366);
+ FMT('0', 3, "d", (int)i);
continue;
case 'm': /* month, 01 - 12 */
@@ -454,10 +550,11 @@ rb_strftime_with_timespec(char *s, size_t maxsize, const char *format, rb_encodi
else {
off = NUM2LONG(rb_funcall(vtm->utc_offset, rb_intern("round"), 0));
}
- if (off < 0) {
+ if (off < 0 || (gmt && (flags & BIT_OF(LEFT)))) {
off = -off;
sign = -1;
- } else {
+ }
+ else {
sign = +1;
}
switch (colons) {
@@ -496,7 +593,7 @@ rb_strftime_with_timespec(char *s, size_t maxsize, const char *format, rb_encodi
goto unknown;
}
i = snprintf(s, endp - s, (padding == ' ' ? "%+*ld" : "%+.*ld"),
- precision + 1, sign * (off / 3600));
+ precision + (padding == ' '), sign * (off / 3600));
if (i < 0) goto err;
if (sign < 0 && off < 3600) {
*(padding == ' ' ? s + i - 2 : s) = '-';
@@ -532,11 +629,14 @@ rb_strftime_with_timespec(char *s, size_t maxsize, const char *format, rb_encodi
tp = "UTC";
break;
}
- if (vtm->zone == NULL) {
+ if (NIL_P(vtm->zone)) {
i = 0;
}
else {
- tp = vtm->zone;
+ if (NIL_P(zone)) {
+ zone = rb_time_zone_abbreviation(vtm->zone, time);
+ }
+ tp = RSTRING_PTR(zone);
if (enc) {
for (i = 0; i < TBUFSIZE && tp[i]; i++) {
if ((unsigned char)tp[i] > 0x7F) {
@@ -734,7 +834,8 @@ rb_strftime_with_timespec(char *s, size_t maxsize, const char *format, rb_encodi
VALUE args[2], result;
args[0] = INT2FIX(precision);
args[1] = subsec;
- result = rb_str_format(2, args, rb_str_new2("%0*d"));
+ result = rb_str_format(2, args,
+ rb_fstring_lit("%0*d"));
(void)strlcpy(s, StringValueCStr(result), endp-s);
s += precision;
}
@@ -767,12 +868,12 @@ rb_strftime_with_timespec(char *s, size_t maxsize, const char *format, rb_encodi
goto again;
case ':':
- {
- size_t l = strspn(format, ":");
- if (l > 3 || format[l] != 'z') goto unknown;
- colons = (int)l;
- format += l - 1;
+ for (colons = 1; colons <= 3; ++colons) {
+ if (format+colons >= format_end) goto unknown;
+ if (format[colons] == 'z') break;
+ if (format[colons] != ':') goto unknown;
}
+ format += colons - 1;
goto again;
case '0':
@@ -780,9 +881,12 @@ rb_strftime_with_timespec(char *s, size_t maxsize, const char *format, rb_encodi
case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
{
- char *e;
- precision = (int)strtoul(format, &e, 10);
- format = e - 1;
+ size_t n;
+ int ov;
+ unsigned long u = ruby_scan_digits(format, format_end-format, 10, &n, &ov);
+ if (ov || u > INT_MAX) goto unknown;
+ precision = (int)u;
+ format += n - 1;
goto again;
}
@@ -799,45 +903,64 @@ rb_strftime_with_timespec(char *s, size_t maxsize, const char *format, rb_encodi
if (i) {
FILL_PADDING(i);
memcpy(s, tp, i);
- switch (flags & (BIT_OF(UPPER)|BIT_OF(LOWER))) {
- case BIT_OF(UPPER):
- do {
- if (ISLOWER(*s)) *s = TOUPPER(*s);
- } while (s++, --i);
- break;
- case BIT_OF(LOWER):
- do {
- if (ISUPPER(*s)) *s = TOLOWER(*s);
- } while (s++, --i);
- break;
- default:
- s += i;
- break;
- }
+ s = case_conv(s, i, flags);
}
}
- if (s >= endp) {
- goto err;
- }
- if (*format == '\0') {
- *s = '\0';
- return (s - start);
- } else
+ if (format != format_end) {
return 0;
+ }
+ len = s - start;
+ rb_str_set_len(ftime, len);
+ rb_str_resize(ftime, len);
+ return ftime;
+
+err:
+ return 0;
}
-size_t
-rb_strftime(char *s, size_t maxsize, const char *format, rb_encoding *enc, const struct vtm *vtm, VALUE timev, int gmt)
+static size_t
+strftime_size_limit(size_t format_len)
{
- return rb_strftime_with_timespec(s, maxsize, format, enc, vtm, timev, NULL, gmt);
+ size_t limit = format_len * (1*1024*1024);
+ if (limit < format_len) limit = format_len;
+ else if (limit < 1024) limit = 1024;
+ return limit;
}
-size_t
-rb_strftime_timespec(char *s, size_t maxsize, const char *format, rb_encoding *enc, const struct vtm *vtm, struct timespec *ts, int gmt)
+VALUE
+rb_strftime(const char *format, size_t format_len, rb_encoding *enc,
+ VALUE time, const struct vtm *vtm, VALUE timev, int gmt)
{
- return rb_strftime_with_timespec(s, maxsize, format, enc, vtm, Qnil, ts, gmt);
+ VALUE result = rb_enc_str_new(0, 0, enc);
+ ENC_CODERANGE_CLEAR(result);
+ return rb_strftime_with_timespec(result, format, format_len, enc,
+ time, vtm, timev, NULL, gmt,
+ strftime_size_limit(format_len));
}
+VALUE
+rb_strftime_timespec(const char *format, size_t format_len, rb_encoding *enc,
+ VALUE time, const struct vtm *vtm, struct timespec *ts, int gmt)
+{
+ VALUE result = rb_enc_str_new(0, 0, enc);
+ ENC_CODERANGE_CLEAR(result);
+ return rb_strftime_with_timespec(result, format, format_len, enc,
+ time, vtm, Qnil, ts, gmt,
+ strftime_size_limit(format_len));
+}
+
+#if 0
+VALUE
+rb_strftime_limit(const char *format, size_t format_len, rb_encoding *enc,
+ VALUE time, const struct vtm *vtm, struct timespec *ts,
+ int gmt, size_t maxsize)
+{
+ VALUE result = rb_enc_str_new(0, 0, enc);
+ return rb_strftime_with_timespec(result, format, format_len, enc,
+ time, vtm, Qnil, ts, gmt, maxsize);
+}
+#endif
+
/* isleap --- is a year a leap year? */
static int
@@ -864,10 +987,10 @@ vtm2tm_noyear(const struct vtm *vtm, struct tm *result)
tm.tm_yday = vtm->yday-1;
tm.tm_isdst = vtm->isdst;
#if defined(HAVE_STRUCT_TM_TM_GMTOFF)
- tm.tm_gmtoff = NUM2LONG(vtm->utc_offset);
+ tm.tm_gmtoff = 0;
#endif
#if defined(HAVE_TM_ZONE)
- tm.tm_zone = (char *)vtm->zone;
+ tm.tm_zone = NULL;
#endif
*result = tm;
}