Ruby 3.5.0dev (2025-01-10 revision 5fab31b15e32622c4b71d1d347a41937e9f9c212)
strftime.c (5fab31b15e32622c4b71d1d347a41937e9f9c212)
1/* -*- c-file-style: "linux" -*- */
2
3/*
4 * strftime.c
5 *
6 * Public-domain implementation of ANSI C library routine.
7 *
8 * It's written in old-style C for maximal portability.
9 * However, since I'm used to prototypes, I've included them too.
10 *
11 * If you want stuff in the System V ascftime routine, add the SYSV_EXT define.
12 * For extensions from SunOS, add SUNOS_EXT.
13 * For stuff needed to implement the P1003.2 date command, add POSIX2_DATE.
14 * For VMS dates, add VMS_EXT.
15 * For a an RFC822 time format, add MAILHEADER_EXT.
16 * For ISO week years, add ISO_DATE_EXT.
17 * For complete POSIX semantics, add POSIX_SEMANTICS.
18 *
19 * The code for %c, %x, and %X now follows the 1003.2 specification for
20 * the POSIX locale.
21 * This version ignores LOCALE information.
22 * It also doesn't worry about multi-byte characters.
23 * So there.
24 *
25 * This file is also shipped with GAWK (GNU Awk), gawk specific bits of
26 * code are included if GAWK is defined.
27 *
28 * Arnold Robbins
29 * January, February, March, 1991
30 * Updated March, April 1992
31 * Updated April, 1993
32 * Updated February, 1994
33 * Updated May, 1994
34 * Updated January, 1995
35 * Updated September, 1995
36 * Updated January, 1996
37 *
38 * Fixes from ado@elsie.nci.nih.gov
39 * February 1991, May 1992
40 * Fixes from Tor Lillqvist tml@tik.vtt.fi
41 * May, 1993
42 * Further fixes from ado@elsie.nci.nih.gov
43 * February 1994
44 * %z code from chip@chinacat.unicom.com
45 * Applied September 1995
46 * %V code fixed (again) and %G, %g added,
47 * January 1996
48 */
49
50#include "ruby/internal/config.h"
51
52#ifndef GAWK
53#include <stdio.h>
54#include <ctype.h>
55#include <string.h>
56#include <time.h>
57#include <sys/types.h>
58#include <errno.h>
59#endif
60#if defined(TM_IN_SYS_TIME) || !defined(GAWK)
61#include <sys/types.h>
62#ifdef HAVE_SYS_TIME_H
63#include <sys/time.h>
64#endif
65#endif
66#include <math.h>
67
68#include "internal.h"
69#include "internal/encoding.h"
70#include "internal/string.h"
71#include "internal/vm.h"
72#include "ruby/encoding.h"
73#include "ruby/ruby.h"
74#include "ruby/util.h"
75#include "timev.h"
76
77/* defaults: season to taste */
78#define SYSV_EXT 1 /* stuff in System V ascftime routine */
79#define SUNOS_EXT 1 /* stuff in SunOS strftime routine */
80#define POSIX2_DATE 1 /* stuff in Posix 1003.2 date command */
81#define VMS_EXT 1 /* include %v for VMS date format */
82#define MAILHEADER_EXT 1 /* add %z for HHMM format */
83#define ISO_DATE_EXT 1 /* %G and %g for year of ISO week */
84
85#if defined(ISO_DATE_EXT)
86#if ! defined(POSIX2_DATE)
87#define POSIX2_DATE 1
88#endif
89#endif
90
91#if defined(POSIX2_DATE)
92#if ! defined(SYSV_EXT)
93#define SYSV_EXT 1
94#endif
95#if ! defined(SUNOS_EXT)
96#define SUNOS_EXT 1
97#endif
98#endif
99
100#if defined(POSIX2_DATE)
101#define adddecl(stuff) stuff
102#else
103#define adddecl(stuff)
104#endif
105
106#undef strchr /* avoid AIX weirdness */
107
108#if !defined __STDC__ && !defined _WIN32
109#define const
110static int weeknumber();
111adddecl(static int iso8601wknum();)
112static int weeknumber_v();
113adddecl(static int iso8601wknum_v();)
114#else
115static int weeknumber(const struct tm *timeptr, int firstweekday);
116adddecl(static int iso8601wknum(const struct tm *timeptr);)
117static int weeknumber_v(const struct vtm *vtm, int firstweekday);
118adddecl(static int iso8601wknum_v(const struct vtm *vtm);)
119#endif
120
121#ifdef STDC_HEADERS
122#include <stdlib.h>
123#include <string.h>
124#else
125extern void *malloc();
126extern void *realloc();
127extern char *getenv();
128extern char *strchr();
129#endif
130
131#define range(low, item, hi) max((low), min((item), (hi)))
132
133#undef min /* just in case */
134
135/* min --- return minimum of two numbers */
136
137static inline int
138min(int a, int b)
139{
140 return (a < b ? a : b);
141}
142
143#undef max /* also, just in case */
144
145/* max --- return maximum of two numbers */
146
147static inline int
148max(int a, int b)
149{
150 return (a > b ? a : b);
151}
152
153#ifdef NO_STRING_LITERAL_CONCATENATION
154#error No string literal concatenation
155#endif
156
157#define add(x,y) (rb_funcall((x), '+', 1, (y)))
158#define sub(x,y) (rb_funcall((x), '-', 1, (y)))
159#define mul(x,y) (rb_funcall((x), '*', 1, (y)))
160#define quo(x,y) (rb_funcall((x), rb_intern("quo"), 1, (y)))
161#define div(x,y) (rb_funcall((x), rb_intern("div"), 1, (y)))
162#define mod(x,y) (rb_funcall((x), '%', 1, (y)))
163
164/* strftime --- produce formatted time */
165
166enum {LEFT, CHCASE, LOWER, UPPER};
167#define BIT_OF(n) (1U<<(n))
168
169static char *
170resize_buffer(VALUE ftime, char *s, const char **start, const char **endp,
171 ptrdiff_t n, size_t maxsize)
172{
173 size_t len = s - *start;
174 size_t need = len + n * 2;
175 size_t nlen = rb_str_capacity(ftime);
176 while (nlen < need) nlen <<= 1;
177
178 if (nlen < len || nlen > maxsize) {
179 return 0;
180 }
181 rb_str_set_len(ftime, len);
182 rb_str_modify_expand(ftime, nlen-len);
183 s = RSTRING_PTR(ftime);
184 *endp = s + nlen;
185 *start = s;
186 return s += len;
187}
188
189static void
190buffer_size_check(const char *s,
191 const char *format_end, size_t format_len,
192 rb_encoding *enc)
193{
194 if (!s) {
195 const char *format = format_end-format_len;
196 VALUE fmt = rb_enc_str_new(format, format_len, enc);
197 rb_syserr_fail_str(ERANGE, fmt);
198 }
199}
200
201static char *
202case_conv(char *s, ptrdiff_t i, int flags)
203{
204 switch (flags & (BIT_OF(UPPER)|BIT_OF(LOWER))) {
205 case BIT_OF(UPPER):
206 do {
207 if (ISLOWER(*s)) *s = TOUPPER(*s);
208 } while (s++, --i);
209 break;
210 case BIT_OF(LOWER):
211 do {
212 if (ISUPPER(*s)) *s = TOLOWER(*s);
213 } while (s++, --i);
214 break;
215 default:
216 s += i;
217 break;
218 }
219 return s;
220}
221
222static VALUE
223format_value(VALUE val, int base)
224{
225 if (!RB_BIGNUM_TYPE_P(val))
226 val = rb_Integer(val);
227 return rb_big2str(val, base);
228}
229
230/*
231 * enc is the encoding of the format. It is used as the encoding of resulted
232 * string, but the name of the month and weekday are always US-ASCII. So it
233 * is only used for the timezone name on Windows.
234 */
235static VALUE
236rb_strftime_with_timespec(VALUE ftime, const char *format, size_t format_len,
237 rb_encoding *enc, VALUE time, const struct vtm *vtm,
238 VALUE timev, struct timespec *ts, int gmt, size_t maxsize)
239{
240 size_t len = RSTRING_LEN(ftime);
241 char *s = RSTRING_PTR(ftime);
242 const char *start = s;
243 const char *endp = start + rb_str_capacity(ftime);
244 const char *const format_end = format + format_len;
245 const char *sp, *tp;
246#define TBUFSIZE 100
247 auto char tbuf[TBUFSIZE];
248 long off;
249 ptrdiff_t i;
250 int w;
251 long y;
252 int precision, flags, colons;
253 char padding;
254#ifdef MAILHEADER_EXT
255 int sign;
256#endif
257 VALUE zone = Qnil;
258
259 /* various tables, useful in North America */
260 static const char days_l[][10] = {
261 "Sunday", "Monday", "Tuesday", "Wednesday",
262 "Thursday", "Friday", "Saturday",
263 };
264 static const char months_l[][10] = {
265 "January", "February", "March", "April",
266 "May", "June", "July", "August", "September",
267 "October", "November", "December",
268 };
269 static const char ampm[][3] = { "AM", "PM", };
270
271 if (format == NULL || format_len == 0 || vtm == NULL) {
272 goto err;
273 }
274
275 if (enc &&
276 (rb_is_usascii_enc(enc) ||
277 rb_is_ascii8bit_enc(enc) ||
278 rb_is_locale_enc(enc))) {
279 enc = NULL;
280 }
281
282 s += len;
283 for (; format < format_end; format++) {
284#define FLAG_FOUND() do { \
285 if (precision > 0) \
286 goto unknown; \
287 } while (0)
288#define NEEDS(n) do { \
289 if (s >= endp || (n) >= endp - s - 1) { \
290 s = resize_buffer(ftime, s, &start, &endp, (n), maxsize); \
291 buffer_size_check(s, format_end, format_len, enc); \
292 } \
293 } while (0)
294#define FILL_PADDING(i) do { \
295 if (!(flags & BIT_OF(LEFT)) && precision > (i)) { \
296 NEEDS(precision); \
297 memset(s, padding ? padding : ' ', precision - (i)); \
298 s += precision - (i); \
299 } \
300 else { \
301 NEEDS(i); \
302 } \
303} while (0);
304#define FMT_PADDING(fmt, def_pad) \
305 (&"%*"fmt"\0""%0*"fmt[\
306 (padding == '0' || (!padding && (def_pad) == '0')) ? \
307 rb_strlen_lit("%*"fmt)+1 : 0])
308#define FMT_PRECISION(def_prec) \
309 ((flags & BIT_OF(LEFT)) ? (1) : \
310 (precision <= 0) ? (def_prec) : (precision))
311#define FMT(def_pad, def_prec, fmt, val) \
312 do { \
313 precision = FMT_PRECISION(def_prec); \
314 len = s - start; \
315 NEEDS(precision); \
316 rb_str_set_len(ftime, len); \
317 rb_str_catf(ftime, FMT_PADDING(fmt, def_pad), \
318 precision, (val)); \
319 RSTRING_GETMEM(ftime, s, len); \
320 endp = (start = s) + rb_str_capacity(ftime); \
321 s += len; \
322 } while (0)
323#define STRFTIME(fmt) \
324 do { \
325 len = s - start; \
326 rb_str_set_len(ftime, len); \
327 if (!rb_strftime_with_timespec(ftime, (fmt), rb_strlen_lit(fmt), \
328 enc, time, vtm, timev, ts, gmt, maxsize)) \
329 return 0; \
330 s = RSTRING_PTR(ftime); \
331 i = RSTRING_LEN(ftime) - len; \
332 endp = (start = s) + rb_str_capacity(ftime); \
333 s += len; \
334 if (i > 0) case_conv(s, i, flags); \
335 if (precision > i) {\
336 s += i; \
337 NEEDS(precision); \
338 s -= i; \
339 memmove(s + precision - i, s, i);\
340 memset(s, padding ? padding : ' ', precision - i); \
341 s += precision; \
342 } \
343 else s += i; \
344 } while (0)
345#define FMTV(def_pad, def_prec, fmt, val) \
346 do { \
347 VALUE tmp = (val); \
348 if (FIXNUM_P(tmp)) { \
349 FMT((def_pad), (def_prec), "l"fmt, FIX2LONG(tmp)); \
350 } \
351 else { \
352 const int base = ((fmt[0] == 'x') ? 16 : \
353 (fmt[0] == 'o') ? 8 : \
354 10); \
355 precision = FMT_PRECISION(def_prec); \
356 if (!padding) padding = (def_pad); \
357 tmp = format_value(tmp, base); \
358 i = RSTRING_LEN(tmp); \
359 FILL_PADDING(i); \
360 rb_str_set_len(ftime, s-start); \
361 rb_str_append(ftime, tmp); \
362 RSTRING_GETMEM(ftime, s, len); \
363 endp = (start = s) + rb_str_capacity(ftime); \
364 s += len; \
365 } \
366 } while (0)
367
368 tp = memchr(format, '%', format_end - format);
369 if (!tp) tp = format_end;
370 NEEDS(tp - format);
371 memcpy(s, format, tp - format);
372 s += tp - format;
373 format = tp;
374 if (format == format_end) break;
375
376 tp = tbuf;
377 sp = format;
378 precision = -1;
379 flags = 0;
380 padding = 0;
381 colons = 0;
382 again:
383 if (++format >= format_end) goto unknown;
384 switch (*format) {
385 case '%':
386 FILL_PADDING(1);
387 *s++ = '%';
388 continue;
389
390 case 'a': /* abbreviated weekday name */
391 if (flags & BIT_OF(CHCASE)) {
392 flags &= ~(BIT_OF(LOWER)|BIT_OF(CHCASE));
393 flags |= BIT_OF(UPPER);
394 }
395 if (vtm->wday > 6)
396 i = 1, tp = "?";
397 else
398 i = 3, tp = days_l[vtm->wday];
399 break;
400
401 case 'A': /* full weekday name */
402 if (flags & BIT_OF(CHCASE)) {
403 flags &= ~(BIT_OF(LOWER)|BIT_OF(CHCASE));
404 flags |= BIT_OF(UPPER);
405 }
406 if (vtm->wday > 6)
407 i = 1, tp = "?";
408 else
409 i = strlen(tp = days_l[vtm->wday]);
410 break;
411
412#ifdef SYSV_EXT
413 case 'h': /* abbreviated month name */
414#endif
415 case 'b': /* abbreviated month name */
416 if (flags & BIT_OF(CHCASE)) {
417 flags &= ~(BIT_OF(LOWER)|BIT_OF(CHCASE));
418 flags |= BIT_OF(UPPER);
419 }
420 if (vtm->mon < 1 || vtm->mon > 12)
421 i = 1, tp = "?";
422 else
423 i = 3, tp = months_l[vtm->mon-1];
424 break;
425
426 case 'B': /* full month name */
427 if (flags & BIT_OF(CHCASE)) {
428 flags &= ~(BIT_OF(LOWER)|BIT_OF(CHCASE));
429 flags |= BIT_OF(UPPER);
430 }
431 if (vtm->mon < 1 || vtm->mon > 12)
432 i = 1, tp = "?";
433 else
434 i = strlen(tp = months_l[vtm->mon-1]);
435 break;
436
437 case 'c': /* appropriate date and time representation */
438 STRFTIME("%a %b %e %H:%M:%S %Y");
439 continue;
440
441 case 'd': /* day of the month, 01 - 31 */
442 i = range(1, vtm->mday, 31);
443 FMT('0', 2, "d", (int)i);
444 continue;
445
446 case 'H': /* hour, 24-hour clock, 00 - 23 */
447 i = range(0, vtm->hour, 23);
448 FMT('0', 2, "d", (int)i);
449 continue;
450
451 case 'I': /* hour, 12-hour clock, 01 - 12 */
452 i = range(0, vtm->hour, 23);
453 if (i == 0)
454 i = 12;
455 else if (i > 12)
456 i -= 12;
457 FMT('0', 2, "d", (int)i);
458 continue;
459
460 case 'j': /* day of the year, 001 - 366 */
461 i = range(1, vtm->yday, 366);
462 FMT('0', 3, "d", (int)i);
463 continue;
464
465 case 'm': /* month, 01 - 12 */
466 i = range(1, vtm->mon, 12);
467 FMT('0', 2, "d", (int)i);
468 continue;
469
470 case 'M': /* minute, 00 - 59 */
471 i = range(0, vtm->min, 59);
472 FMT('0', 2, "d", (int)i);
473 continue;
474
475 case 'p': /* AM or PM based on 12-hour clock */
476 case 'P': /* am or pm based on 12-hour clock */
477 if ((*format == 'p' && (flags & BIT_OF(CHCASE))) ||
478 (*format == 'P' && !(flags & (BIT_OF(CHCASE)|BIT_OF(UPPER))))) {
479 flags &= ~(BIT_OF(UPPER)|BIT_OF(CHCASE));
480 flags |= BIT_OF(LOWER);
481 }
482 i = range(0, vtm->hour, 23);
483 if (i < 12)
484 tp = ampm[0];
485 else
486 tp = ampm[1];
487 i = 2;
488 break;
489
490 case 's':
491 if (ts) {
492 time_t sec = ts->tv_sec;
493 if (~(time_t)0 <= 0)
494 FMT('0', 1, PRI_TIMET_PREFIX"d", sec);
495 else
496 FMT('0', 1, PRI_TIMET_PREFIX"u", sec);
497 }
498 else {
499 VALUE sec = div(timev, INT2FIX(1));
500 FMTV('0', 1, "d", sec);
501 }
502 continue;
503
504 case 'S': /* second, 00 - 60 */
505 i = range(0, vtm->sec, 60);
506 FMT('0', 2, "d", (int)i);
507 continue;
508
509 case 'U': /* week of year, Sunday is first day of week */
510 FMT('0', 2, "d", weeknumber_v(vtm, 0));
511 continue;
512
513 case 'w': /* weekday, Sunday == 0, 0 - 6 */
514 i = range(0, vtm->wday, 6);
515 FMT('0', 1, "d", (int)i);
516 continue;
517
518 case 'W': /* week of year, Monday is first day of week */
519 FMT('0', 2, "d", weeknumber_v(vtm, 1));
520 continue;
521
522 case 'x': /* appropriate date representation */
523 STRFTIME("%m/%d/%y");
524 continue;
525
526 case 'X': /* appropriate time representation */
527 STRFTIME("%H:%M:%S");
528 continue;
529
530 case 'y': /* year without a century, 00 - 99 */
531 i = NUM2INT(mod(vtm->year, INT2FIX(100)));
532 FMT('0', 2, "d", (int)i);
533 continue;
534
535 case 'Y': /* year with century */
536 if (FIXNUM_P(vtm->year)) {
537 long y = FIX2LONG(vtm->year);
538 FMT('0', 0 <= y ? 4 : 5, "ld", y);
539 }
540 else {
541 FMTV('0', 4, "d", vtm->year);
542 }
543 continue;
544
545#ifdef MAILHEADER_EXT
546 case 'z': /* time zone offset east of GMT e.g. -0600 */
547 if (gmt) {
548 off = 0;
549 }
550 else {
551 off = NUM2LONG(rb_funcall(vtm->utc_offset, rb_intern("round"), 0));
552 }
553 if (off < 0 || (gmt && (flags & BIT_OF(LEFT)))) {
554 off = -off;
555 sign = -1;
556 }
557 else {
558 sign = +1;
559 }
560 switch (colons) {
561 case 0: /* %z -> +hhmm */
562 precision = precision <= 5 ? 2 : precision-3;
563 NEEDS(precision + 3);
564 break;
565
566 case 1: /* %:z -> +hh:mm */
567 precision = precision <= 6 ? 2 : precision-4;
568 NEEDS(precision + 4);
569 break;
570
571 case 2: /* %::z -> +hh:mm:ss */
572 precision = precision <= 9 ? 2 : precision-7;
573 NEEDS(precision + 7);
574 break;
575
576 case 3: /* %:::z -> +hh[:mm[:ss]] */
577 if (off % 3600 == 0) {
578 precision = precision <= 3 ? 2 : precision-1;
579 NEEDS(precision + 3);
580 }
581 else if (off % 60 == 0) {
582 precision = precision <= 6 ? 2 : precision-4;
583 NEEDS(precision + 4);
584 }
585 else {
586 precision = precision <= 9 ? 2 : precision-7;
587 NEEDS(precision + 9);
588 }
589 break;
590
591 default:
592 format--;
593 goto unknown;
594 }
595 i = snprintf(s, endp - s, (padding == ' ' ? "%+*ld" : "%+.*ld"),
596 precision + (padding == ' '), sign * (off / 3600));
597 if (i < 0) goto err;
598 if (sign < 0 && off < 3600) {
599 *(padding == ' ' ? s + i - 2 : s) = '-';
600 }
601 s += i;
602 off = off % 3600;
603 if (colons == 3 && off == 0)
604 continue;
605 if (1 <= colons)
606 *s++ = ':';
607 i = snprintf(s, endp - s, "%02d", (int)(off / 60));
608 if (i < 0) goto err;
609 s += i;
610 off = off % 60;
611 if (colons == 3 && off == 0)
612 continue;
613 if (2 <= colons) {
614 *s++ = ':';
615 i = snprintf(s, endp - s, "%02d", (int)off);
616 if (i < 0) goto err;
617 s += i;
618 }
619 continue;
620#endif /* MAILHEADER_EXT */
621
622 case 'Z': /* time zone name or abbreviation */
623 if (flags & BIT_OF(CHCASE)) {
624 flags &= ~(BIT_OF(UPPER)|BIT_OF(CHCASE));
625 flags |= BIT_OF(LOWER);
626 }
627 if (gmt) {
628 i = 3;
629 tp = "UTC";
630 break;
631 }
632 if (NIL_P(vtm->zone)) {
633 i = 0;
634 }
635 else {
636 if (NIL_P(zone)) {
637 zone = rb_time_zone_abbreviation(vtm->zone, time);
638 }
639 tp = RSTRING_PTR(zone);
640 if (enc) {
641 for (i = 0; i < TBUFSIZE && tp[i]; i++) {
642 if ((unsigned char)tp[i] > 0x7F) {
644 i = strlcpy(tbuf, RSTRING_PTR(str), TBUFSIZE);
645 tp = tbuf;
646 break;
647 }
648 }
649 }
650 else
651 i = strlen(tp);
652 }
653 break;
654
655#ifdef SYSV_EXT
656 case 'n': /* same as \n */
657 FILL_PADDING(1);
658 *s++ = '\n';
659 continue;
660
661 case 't': /* same as \t */
662 FILL_PADDING(1);
663 *s++ = '\t';
664 continue;
665
666 case 'D': /* date as %m/%d/%y */
667 STRFTIME("%m/%d/%y");
668 continue;
669
670 case 'e': /* day of month, blank padded */
671 FMT(' ', 2, "d", range(1, vtm->mday, 31));
672 continue;
673
674 case 'r': /* time as %I:%M:%S %p */
675 STRFTIME("%I:%M:%S %p");
676 continue;
677
678 case 'R': /* time as %H:%M */
679 STRFTIME("%H:%M");
680 continue;
681
682 case 'T': /* time as %H:%M:%S */
683 STRFTIME("%H:%M:%S");
684 continue;
685#endif
686
687#ifdef SUNOS_EXT
688 case 'k': /* hour, 24-hour clock, blank pad */
689 i = range(0, vtm->hour, 23);
690 FMT(' ', 2, "d", (int)i);
691 continue;
692
693 case 'l': /* hour, 12-hour clock, 1 - 12, blank pad */
694 i = range(0, vtm->hour, 23);
695 if (i == 0)
696 i = 12;
697 else if (i > 12)
698 i -= 12;
699 FMT(' ', 2, "d", (int)i);
700 continue;
701#endif
702
703
704#ifdef VMS_EXT
705 case 'v': /* date as dd-bbb-YYYY */
706 STRFTIME("%e-%^b-%4Y");
707 continue;
708#endif
709
710
711#ifdef POSIX2_DATE
712 case 'C':
713 FMTV('0', 2, "d", div(vtm->year, INT2FIX(100)));
714 continue;
715
716 case 'E':
717 /* POSIX locale extensions, ignored for now */
718 if (!format[1] || !strchr("cCxXyY", format[1]))
719 goto unknown;
720 goto again;
721 case 'O':
722 /* POSIX locale extensions, ignored for now */
723 if (!format[1] || !strchr("deHkIlmMSuUVwWy", format[1]))
724 goto unknown;
725 goto again;
726
727 case 'V': /* week of year according ISO 8601 */
728 FMT('0', 2, "d", iso8601wknum_v(vtm));
729 continue;
730
731 case 'u':
732 /* ISO 8601: Weekday as a decimal number [1 (Monday) - 7] */
733 FMT('0', 1, "d", vtm->wday == 0 ? 7 : vtm->wday);
734 continue;
735#endif /* POSIX2_DATE */
736
737#ifdef ISO_DATE_EXT
738 case 'G':
739 case 'g':
740 /*
741 * Year of ISO week.
742 *
743 * If it's December but the ISO week number is one,
744 * that week is in next year.
745 * If it's January but the ISO week number is 52 or
746 * 53, that week is in last year.
747 * Otherwise, it's this year.
748 */
749 {
750 VALUE yv = vtm->year;
751 w = iso8601wknum_v(vtm);
752 if (vtm->mon == 12 && w == 1)
753 yv = add(yv, INT2FIX(1));
754 else if (vtm->mon == 1 && w >= 52)
755 yv = sub(yv, INT2FIX(1));
756
757 if (*format == 'G') {
758 if (FIXNUM_P(yv)) {
759 const long y = FIX2LONG(yv);
760 FMT('0', 0 <= y ? 4 : 5, "ld", y);
761 }
762 else {
763 FMTV('0', 4, "d", yv);
764 }
765 }
766 else {
767 yv = mod(yv, INT2FIX(100));
768 y = FIX2LONG(yv);
769 FMT('0', 2, "ld", y);
770 }
771 continue;
772 }
773
774#endif /* ISO_DATE_EXT */
775
776
777 case 'L':
778 w = 3;
779 goto subsec;
780
781 case 'N':
782 /*
783 * fractional second digits. default is 9 digits
784 * (nanosecond).
785 *
786 * %3N millisecond (3 digits)
787 * %6N microsecond (6 digits)
788 * %9N nanosecond (9 digits)
789 */
790 w = 9;
791 subsec:
792 if (precision <= 0) {
793 precision = w;
794 }
795 NEEDS(precision);
796
797 if (ts) {
798 long subsec = ts->tv_nsec;
799 if (9 < precision) {
800 snprintf(s, endp - s, "%09ld", subsec);
801 memset(s+9, '0', precision-9);
802 s += precision;
803 }
804 else {
805 int i;
806 for (i = 0; i < 9-precision; i++)
807 subsec /= 10;
808 snprintf(s, endp - s, "%0*ld", precision, subsec);
809 s += precision;
810 }
811 }
812 else {
813 VALUE subsec = mod(timev, INT2FIX(1));
814 int ww;
815 long n;
816
817 ww = precision;
818 while (9 <= ww) {
819 subsec = mul(subsec, INT2FIX(1000000000));
820 ww -= 9;
821 }
822 n = 1;
823 for (; 0 < ww; ww--)
824 n *= 10;
825 if (n != 1)
826 subsec = mul(subsec, INT2FIX(n));
827 subsec = div(subsec, INT2FIX(1));
828
829 if (FIXNUM_P(subsec)) {
830 (void)snprintf(s, endp - s, "%0*ld", precision, FIX2LONG(subsec));
831 s += precision;
832 }
833 else {
834 VALUE args[2], result;
835 args[0] = INT2FIX(precision);
836 args[1] = subsec;
837 result = rb_str_format(2, args,
838 rb_fstring_lit("%0*d"));
839 (void)strlcpy(s, StringValueCStr(result), endp-s);
840 s += precision;
841 }
842 }
843 continue;
844
845 case 'F': /* Equivalent to %Y-%m-%d */
846 STRFTIME("%Y-%m-%d");
847 continue;
848
849 case '-':
850 FLAG_FOUND();
851 flags |= BIT_OF(LEFT);
852 padding = precision = 0;
853 goto again;
854
855 case '^':
856 FLAG_FOUND();
857 flags |= BIT_OF(UPPER);
858 goto again;
859
860 case '#':
861 FLAG_FOUND();
862 flags |= BIT_OF(CHCASE);
863 goto again;
864
865 case '_':
866 FLAG_FOUND();
867 padding = ' ';
868 goto again;
869
870 case ':':
871 for (colons = 1; colons <= 3; ++colons) {
872 if (format+colons >= format_end) goto unknown;
873 if (format[colons] == 'z') break;
874 if (format[colons] != ':') goto unknown;
875 }
876 format += colons - 1;
877 goto again;
878
879 case '0':
880 padding = '0';
881 case '1': case '2': case '3': case '4':
882 case '5': case '6': case '7': case '8': case '9':
883 {
884 size_t n;
885 int ov;
886 unsigned long u = ruby_scan_digits(format, format_end-format, 10, &n, &ov);
887 if (ov || u > INT_MAX) goto unknown;
888 precision = (int)u;
889 format += n - 1;
890 goto again;
891 }
892
893 default:
894 unknown:
895 i = format - sp + 1;
896 tp = sp;
897 precision = -1;
898 flags = 0;
899 padding = 0;
900 colons = 0;
901 break;
902 }
903 if (i) {
904 FILL_PADDING(i);
905 memcpy(s, tp, i);
906 s = case_conv(s, i, flags);
907 }
908 }
909 if (format != format_end) {
910 return 0;
911 }
912 len = s - start;
913 rb_str_set_len(ftime, len);
914 rb_str_resize(ftime, len);
915 return ftime;
916
917err:
918 return 0;
919}
920
921static size_t
922strftime_size_limit(size_t format_len)
923{
924 size_t limit = format_len * (1*1024*1024);
925 if (limit < format_len) limit = format_len;
926 else if (limit < 1024) limit = 1024;
927 return limit;
928}
929
930VALUE
931rb_strftime(const char *format, size_t format_len, rb_encoding *enc,
932 VALUE time, const struct vtm *vtm, VALUE timev, int gmt)
933{
934 VALUE result = rb_enc_str_new(0, 0, enc);
935 ENC_CODERANGE_CLEAR(result);
936 return rb_strftime_with_timespec(result, format, format_len, enc,
937 time, vtm, timev, NULL, gmt,
938 strftime_size_limit(format_len));
939}
940
941VALUE
942rb_strftime_timespec(const char *format, size_t format_len, rb_encoding *enc,
943 VALUE time, const struct vtm *vtm, struct timespec *ts, int gmt)
944{
945 VALUE result = rb_enc_str_new(0, 0, enc);
946 ENC_CODERANGE_CLEAR(result);
947 return rb_strftime_with_timespec(result, format, format_len, enc,
948 time, vtm, Qnil, ts, gmt,
949 strftime_size_limit(format_len));
950}
951
952#if 0
953VALUE
954rb_strftime_limit(const char *format, size_t format_len, rb_encoding *enc,
955 VALUE time, const struct vtm *vtm, struct timespec *ts,
956 int gmt, size_t maxsize)
957{
958 VALUE result = rb_enc_str_new(0, 0, enc);
959 return rb_strftime_with_timespec(result, format, format_len, enc,
960 time, vtm, Qnil, ts, gmt, maxsize);
961}
962#endif
963
964/* isleap --- is a year a leap year? */
965
966static int
967isleap(long year)
968{
969 return ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0);
970}
971
972
973static void
974vtm2tm_noyear(const struct vtm *vtm, struct tm *result)
975{
976 struct tm tm;
977
978 /* for isleap() in iso8601wknum. +100 is -1900 (mod 400). */
979 tm.tm_year = FIX2INT(mod(vtm->year, INT2FIX(400))) + 100;
980
981 tm.tm_mon = vtm->mon-1;
982 tm.tm_mday = vtm->mday;
983 tm.tm_hour = vtm->hour;
984 tm.tm_min = vtm->min;
985 tm.tm_sec = vtm->sec;
986 tm.tm_wday = vtm->wday;
987 tm.tm_yday = vtm->yday-1;
988 tm.tm_isdst = vtm->isdst;
989#if defined(HAVE_STRUCT_TM_TM_GMTOFF)
990 tm.tm_gmtoff = 0;
991#endif
992#if defined(HAVE_TM_ZONE)
993 tm.tm_zone = NULL;
994#endif
995 *result = tm;
996}
997
998#ifdef POSIX2_DATE
999/* iso8601wknum --- compute week number according to ISO 8601 */
1000
1001static int
1002iso8601wknum(const struct tm *timeptr)
1003{
1004 /*
1005 * From 1003.2:
1006 * If the week (Monday to Sunday) containing January 1
1007 * has four or more days in the new year, then it is week 1;
1008 * otherwise it is the highest numbered week of the previous
1009 * year (52 or 53), and the next week is week 1.
1010 *
1011 * ADR: This means if Jan 1 was Monday through Thursday,
1012 * it was week 1, otherwise week 52 or 53.
1013 *
1014 * XPG4 erroneously included POSIX.2 rationale text in the
1015 * main body of the standard. Thus it requires week 53.
1016 */
1017
1018 int weeknum, jan1day;
1019
1020 /* get week number, Monday as first day of the week */
1021 weeknum = weeknumber(timeptr, 1);
1022
1023 /*
1024 * With thanks and tip of the hatlo to tml@tik.vtt.fi
1025 *
1026 * What day of the week does January 1 fall on?
1027 * We know that
1028 * (timeptr->tm_yday - jan1.tm_yday) MOD 7 ==
1029 * (timeptr->tm_wday - jan1.tm_wday) MOD 7
1030 * and that
1031 * jan1.tm_yday == 0
1032 * and that
1033 * timeptr->tm_wday MOD 7 == timeptr->tm_wday
1034 * from which it follows that. . .
1035 */
1036 jan1day = timeptr->tm_wday - (timeptr->tm_yday % 7);
1037 if (jan1day < 0)
1038 jan1day += 7;
1039
1040 /*
1041 * If Jan 1 was a Monday through Thursday, it was in
1042 * week 1. Otherwise it was last year's highest week, which is
1043 * this year's week 0.
1044 *
1045 * What does that mean?
1046 * If Jan 1 was Monday, the week number is exactly right, it can
1047 * never be 0.
1048 * If it was Tuesday through Thursday, the weeknumber is one
1049 * less than it should be, so we add one.
1050 * Otherwise, Friday, Saturday or Sunday, the week number is
1051 * OK, but if it is 0, it needs to be 52 or 53.
1052 */
1053 switch (jan1day) {
1054 case 1: /* Monday */
1055 break;
1056 case 2: /* Tuesday */
1057 case 3: /* Wednesday */
1058 case 4: /* Thursday */
1059 weeknum++;
1060 break;
1061 case 5: /* Friday */
1062 case 6: /* Saturday */
1063 case 0: /* Sunday */
1064 if (weeknum == 0) {
1065#ifdef USE_BROKEN_XPG4
1066 /* XPG4 (as of March 1994) says 53 unconditionally */
1067 weeknum = 53;
1068#else
1069 /* get week number of last week of last year */
1070 struct tm dec31ly; /* 12/31 last year */
1071 dec31ly = *timeptr;
1072 dec31ly.tm_year--;
1073 dec31ly.tm_mon = 11;
1074 dec31ly.tm_mday = 31;
1075 dec31ly.tm_wday = (jan1day == 0) ? 6 : jan1day - 1;
1076 dec31ly.tm_yday = 364 + isleap(dec31ly.tm_year + 1900L);
1077 weeknum = iso8601wknum(& dec31ly);
1078#endif
1079 }
1080 break;
1081 }
1082
1083 if (timeptr->tm_mon == 11) {
1084 /*
1085 * The last week of the year
1086 * can be in week 1 of next year.
1087 * Sigh.
1088 *
1089 * This can only happen if
1090 * M T W
1091 * 29 30 31
1092 * 30 31
1093 * 31
1094 */
1095 int wday, mday;
1096
1097 wday = timeptr->tm_wday;
1098 mday = timeptr->tm_mday;
1099 if ( (wday == 1 && (mday >= 29 && mday <= 31))
1100 || (wday == 2 && (mday == 30 || mday == 31))
1101 || (wday == 3 && mday == 31))
1102 weeknum = 1;
1103 }
1104
1105 return weeknum;
1106}
1107
1108static int
1109iso8601wknum_v(const struct vtm *vtm)
1110{
1111 struct tm tm;
1112 vtm2tm_noyear(vtm, &tm);
1113 return iso8601wknum(&tm);
1114}
1115
1116#endif
1117
1118/* weeknumber --- figure how many weeks into the year */
1119
1120/* With thanks and tip of the hatlo to ado@elsie.nci.nih.gov */
1121
1122static int
1123weeknumber(const struct tm *timeptr, int firstweekday)
1124{
1125 int wday = timeptr->tm_wday;
1126 int ret;
1127
1128 if (firstweekday == 1) {
1129 if (wday == 0) /* sunday */
1130 wday = 6;
1131 else
1132 wday--;
1133 }
1134 ret = ((timeptr->tm_yday + 7 - wday) / 7);
1135 if (ret < 0)
1136 ret = 0;
1137 return ret;
1138}
1139
1140static int
1141weeknumber_v(const struct vtm *vtm, int firstweekday)
1142{
1143 struct tm tm;
1144 vtm2tm_noyear(vtm, &tm);
1145 return weeknumber(&tm, firstweekday);
1146}
1147
1148#if 0
1149/* ADR --- I'm loathe to mess with ado's code ... */
1150
1151Date: Wed, 24 Apr 91 20:54:08 MDT
1152From: Michal Jaegermann <audfax!emory!vm.ucs.UAlberta.CA!NTOMCZAK>
1153To: arnold@audiofax.com
1154
1155Hi Arnold,
1156in a process of fixing of strftime() in libraries on Atari ST I grabbed
1157some pieces of code from your own strftime. When doing that it came
1158to mind that your weeknumber() function compiles a little bit nicer
1159in the following form:
1160/*
1161 * firstweekday is 0 if starting in Sunday, non-zero if in Monday
1162 */
1163{
1164 return (timeptr->tm_yday - timeptr->tm_wday +
1165 (firstweekday ? (timeptr->tm_wday ? 8 : 1) : 7)) / 7;
1166}
1167How nicer it depends on a compiler, of course, but always a tiny bit.
1168
1169 Cheers,
1170 Michal
1171 ntomczak@vm.ucs.ualberta.ca
1172#endif
1173
1174#ifdef TEST_STRFTIME
1175
1176/*
1177 * NAME:
1178 * tst
1179 *
1180 * SYNOPSIS:
1181 * tst
1182 *
1183 * DESCRIPTION:
1184 * "tst" is a test driver for the function "strftime".
1185 *
1186 * OPTIONS:
1187 * None.
1188 *
1189 * AUTHOR:
1190 * Karl Vogel
1191 * Control Data Systems, Inc.
1192 * vogelke@c-17igp.wpafb.af.mil
1193 *
1194 * BUGS:
1195 * None noticed yet.
1196 *
1197 * COMPILE:
1198 * cc -o tst -DTEST_STRFTIME strftime.c
1199 */
1200
1201/* ADR: I reformatted this to my liking, and deleted some unneeded code. */
1202
1203#ifndef NULL
1204#include <stdio.h>
1205#endif
1206#include <sys/time.h>
1207#include <string.h>
1208
1209#define MAXTIME 132
1210
1211/*
1212 * Array of time formats.
1213 */
1214
1215static char *array[] =
1216{
1217 "(%%A) full weekday name, var length (Sunday..Saturday) %A",
1218 "(%%B) full month name, var length (January..December) %B",
1219 "(%%C) Century %C",
1220 "(%%D) date (%%m/%%d/%%y) %D",
1221 "(%%E) Locale extensions (ignored) %E",
1222 "(%%H) hour (24-hour clock, 00..23) %H",
1223 "(%%I) hour (12-hour clock, 01..12) %I",
1224 "(%%M) minute (00..59) %M",
1225 "(%%O) Locale extensions (ignored) %O",
1226 "(%%R) time, 24-hour (%%H:%%M) %R",
1227 "(%%S) second (00..60) %S",
1228 "(%%T) time, 24-hour (%%H:%%M:%%S) %T",
1229 "(%%U) week of year, Sunday as first day of week (00..53) %U",
1230 "(%%V) week of year according to ISO 8601 %V",
1231 "(%%W) week of year, Monday as first day of week (00..53) %W",
1232 "(%%X) appropriate locale time representation (%H:%M:%S) %X",
1233 "(%%Y) year with century (1970...) %Y",
1234 "(%%Z) timezone (EDT), or blank if timezone not determinable %Z",
1235 "(%%a) locale's abbreviated weekday name (Sun..Sat) %a",
1236 "(%%b) locale's abbreviated month name (Jan..Dec) %b",
1237 "(%%c) full date (Sat Nov 4 12:02:33 1989)%n%t%t%t %c",
1238 "(%%d) day of the month (01..31) %d",
1239 "(%%e) day of the month, blank-padded ( 1..31) %e",
1240 "(%%h) should be same as (%%b) %h",
1241 "(%%j) day of the year (001..366) %j",
1242 "(%%k) hour, 24-hour clock, blank pad ( 0..23) %k",
1243 "(%%l) hour, 12-hour clock, blank pad ( 1..12) %l",
1244 "(%%m) month (01..12) %m",
1245 "(%%p) locale's AM or PM based on 12-hour clock %p",
1246 "(%%r) time, 12-hour (same as %%I:%%M:%%S %%p) %r",
1247 "(%%u) ISO 8601: Weekday as decimal number [1 (Monday) - 7] %u",
1248 "(%%v) VMS date (dd-bbb-YYYY) %v",
1249 "(%%w) day of week (0..6, Sunday == 0) %w",
1250 "(%%x) appropriate locale date representation %x",
1251 "(%%y) last two digits of year (00..99) %y",
1252 "(%%z) timezone offset east of GMT as HHMM (e.g. -0500) %z",
1253 (char *) NULL
1254};
1255
1256/* main routine. */
1257
1258int
1259main(int argc, char **argv)
1260{
1261 long time();
1262
1263 char *next;
1264 char string[MAXTIME];
1265
1266 int k;
1267 int length;
1268
1269 struct tm *tm;
1270
1271 long clock;
1272
1273 /* Call the function. */
1274
1275 clock = time((long *) 0);
1276 tm = localtime(&clock);
1277
1278 for (k = 0; next = array[k]; k++) {
1279 length = strftime(string, MAXTIME, next, tm);
1280 printf("%s\n", string);
1281 }
1282
1283 exit(0);
1284}
1285#endif /* TEST_STRFTIME */
#define INT2FIX
Old name of RB_INT2FIX.
Definition long.h:48
#define ISUPPER
Old name of rb_isupper.
Definition ctype.h:89
#define ECONV_UNDEF_REPLACE
Old name of RUBY_ECONV_UNDEF_REPLACE.
Definition transcode.h:526
#define FIX2INT
Old name of RB_FIX2INT.
Definition int.h:41
#define ECONV_INVALID_REPLACE
Old name of RUBY_ECONV_INVALID_REPLACE.
Definition transcode.h:524
#define TOUPPER
Old name of rb_toupper.
Definition ctype.h:100
#define ISLOWER
Old name of rb_islower.
Definition ctype.h:90
#define TOLOWER
Old name of rb_tolower.
Definition ctype.h:101
#define NUM2INT
Old name of RB_NUM2INT.
Definition int.h:44
#define Qnil
Old name of RUBY_Qnil.
#define FIX2LONG
Old name of RB_FIX2LONG.
Definition long.h:46
#define NIL_P
Old name of RB_NIL_P.
#define NUM2LONG
Old name of RB_NUM2LONG.
Definition long.h:51
#define ENC_CODERANGE_CLEAR(obj)
Old name of RB_ENC_CODERANGE_CLEAR.
Definition coderange.h:187
#define FIXNUM_P
Old name of RB_FIXNUM_P.
void rb_syserr_fail_str(int e, VALUE mesg)
Identical to rb_syserr_fail(), except it takes the message in Ruby's String instead of C's.
Definition error.c:3883
VALUE rb_Integer(VALUE val)
This is the logic behind Kernel#Integer.
Definition object.c:3267
Encoding relates APIs.
VALUE rb_str_conv_enc_opts(VALUE str, rb_encoding *from, rb_encoding *to, int ecflags, VALUE ecopts)
Identical to rb_str_conv_enc(), except it additionally takes IO encoder options.
Definition string.c:1169
VALUE rb_funcall(VALUE recv, ID mid, int n,...)
Calls a method.
Definition vm_eval.c:1099
size_t rb_str_capacity(VALUE str)
Queries the capacity of the given string.
Definition string.c:954
void rb_str_set_len(VALUE str, long len)
Overwrites the length of the string.
Definition string.c:3268
void rb_str_modify_expand(VALUE str, long capa)
Identical to rb_str_modify(), except it additionally expands the capacity of the receiver.
Definition string.c:2648
#define rb_str_new_cstr(str)
Identical to rb_str_new, except it assumes the passed pointer is a pointer to a C string.
Definition string.h:1514
int off
Offset inside of ptr.
Definition io.h:5
int len
Length of the buffer.
Definition io.h:8
VALUE rb_str_format(int argc, const VALUE *argv, VALUE fmt)
Formats a string.
Definition sprintf.c:215
#define StringValueCStr(v)
Identical to StringValuePtr, except it additionally checks for the contents for viability as a C stri...
Definition rstring.h:89
Definition timev.h:5
uintptr_t VALUE
Type that represents a Ruby object.
Definition value.h:40