1 /* -*- c-file-style: "linux" -*- */
6 * Public-domain implementation of ANSI C library routine.
8 * It's written in old-style C for maximal portability.
9 * However, since I'm used to prototypes, I've included them too.
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.
19 * The code for %c, %x, and %X now follows the 1003.2 specification for
21 * This version ignores LOCALE information.
22 * It also doesn't worry about multi-byte characters.
25 * This file is also shipped with GAWK (GNU Awk), gawk specific bits of
26 * code are included if GAWK is defined.
29 * January, February, March, 1991
30 * Updated March, April 1992
32 * Updated February, 1994
34 * Updated January, 1995
35 * Updated September, 1995
36 * Updated January, 1996
38 * Fixes from ado@elsie.nci.nih.gov
39 * February 1991, May 1992
40 * Fixes from Tor Lillqvist tml@tik.vtt.fi
42 * Further fixes from ado@elsie.nci.nih.gov
44 * %z code from chip@chinacat.unicom.com
45 * Applied September 1995
46 * %V code fixed (again) and %G, %g added,
50 #include "ruby/internal/config.h"
57 #include <sys/types.h>
60 #if defined(TM_IN_SYS_TIME) || !defined(GAWK)
61 #include <sys/types.h>
62 #ifdef HAVE_SYS_TIME_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"
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 */
85 #if defined(ISO_DATE_EXT)
86 #if ! defined(POSIX2_DATE)
91 #if defined(POSIX2_DATE)
92 #if ! defined(SYSV_EXT)
95 #if ! defined(SUNOS_EXT)
100 #if defined(POSIX2_DATE)
101 #define adddecl(stuff) stuff
103 #define adddecl(stuff)
106 #undef strchr /* avoid AIX weirdness */
108 #if !defined __STDC__ && !defined _WIN32
110 static int weeknumber();
111 adddecl(static int iso8601wknum();)
112 static int weeknumber_v();
113 adddecl(static int iso8601wknum_v();)
115 static int weeknumber(const struct tm
*timeptr
, int firstweekday
);
116 adddecl(static int iso8601wknum(const struct tm
*timeptr
);)
117 static int weeknumber_v(const struct vtm
*vtm
, int firstweekday
);
118 adddecl(static int iso8601wknum_v(const struct vtm
*vtm
);)
125 extern void *malloc();
126 extern void *realloc();
127 extern char *getenv();
128 extern char *strchr();
131 #define range(low, item, hi) max((low), min((item), (hi)))
133 #undef min /* just in case */
135 /* min --- return minimum of two numbers */
140 return (a
< b
? a
: b
);
143 #undef max /* also, just in case */
145 /* max --- return maximum of two numbers */
150 return (a
> b
? a
: b
);
153 #ifdef NO_STRING_LITERAL_CONCATENATION
154 #error No string literal concatenation
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)))
164 /* strftime --- produce formatted time */
166 enum {LEFT
, CHCASE
, LOWER
, UPPER
};
167 #define BIT_OF(n) (1U<<(n))
170 resize_buffer(VALUE ftime
, char *s
, const char **start
, const char **endp
,
171 ptrdiff_t n
, size_t maxsize
)
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;
178 if (nlen
< len
|| nlen
> maxsize
) {
181 rb_str_set_len(ftime
, len
);
182 rb_str_modify_expand(ftime
, nlen
-len
);
183 s
= RSTRING_PTR(ftime
);
190 buffer_size_check(const char *s
,
191 const char *format_end
, size_t format_len
,
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
);
202 case_conv(char *s
, ptrdiff_t i
, int flags
)
204 switch (flags
& (BIT_OF(UPPER
)|BIT_OF(LOWER
))) {
207 if (ISLOWER(*s
)) *s
= TOUPPER(*s
);
212 if (ISUPPER(*s
)) *s
= TOLOWER(*s
);
223 format_value(VALUE val
, int base
)
225 if (!RB_BIGNUM_TYPE_P(val
))
226 val
= rb_Integer(val
);
227 return rb_big2str(val
, base
);
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.
236 rb_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
)
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
;
247 auto char tbuf
[TBUFSIZE
];
252 int precision
, flags
, colons
;
254 #ifdef MAILHEADER_EXT
259 /* various tables, useful in North America */
260 static const char days_l
[][10] = {
261 "Sunday", "Monday", "Tuesday", "Wednesday",
262 "Thursday", "Friday", "Saturday",
264 static const char months_l
[][10] = {
265 "January", "February", "March", "April",
266 "May", "June", "July", "August", "September",
267 "October", "November", "December",
269 static const char ampm
[][3] = { "AM", "PM", };
271 if (format
== NULL
|| format_len
== 0 || vtm
== NULL
) {
276 (rb_is_usascii_enc(enc
) ||
277 rb_is_ascii8bit_enc(enc
) ||
278 rb_is_locale_enc(enc
))) {
283 for (; format
< format_end
; format
++) {
284 #define FLAG_FOUND() do { \
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); \
294 #define FILL_PADDING(i) do { \
295 if (!(flags & BIT_OF(LEFT)) && precision > (i)) { \
297 memset(s, padding ? padding : ' ', precision - (i)); \
298 s += precision - (i); \
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) \
313 precision = FMT_PRECISION(def_prec); \
316 rb_str_set_len(ftime, len); \
317 rb_str_catf(ftime, FMT_PADDING(fmt, def_pad), \
319 RSTRING_GETMEM(ftime, s, len); \
320 endp = (start = s) + rb_str_capacity(ftime); \
323 #define STRFTIME(fmt) \
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)) \
330 s = RSTRING_PTR(ftime); \
331 i = RSTRING_LEN(ftime) - len; \
332 endp = (start = s) + rb_str_capacity(ftime); \
334 if (i > 0) case_conv(s, i, flags); \
335 if (precision > i) {\
339 memmove(s + precision - i, s, i);\
340 memset(s, padding ? padding : ' ', precision - i); \
345 #define FMTV(def_pad, def_prec, fmt, val) \
348 if (FIXNUM_P(tmp)) { \
349 FMT((def_pad), (def_prec), "l"fmt, FIX2LONG(tmp)); \
352 const int base = ((fmt[0] == 'x') ? 16 : \
353 (fmt[0] == 'o') ? 8 : \
355 precision = FMT_PRECISION(def_prec); \
356 if (!padding) padding = (def_pad); \
357 tmp = format_value(tmp, base); \
358 i = RSTRING_LEN(tmp); \
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); \
368 tp
= memchr(format
, '%', format_end
- format
);
369 if (!tp
) tp
= format_end
;
371 memcpy(s
, format
, tp
- format
);
374 if (format
== format_end
) break;
383 if (++format
>= format_end
) goto unknown
;
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
);
398 i
= 3, tp
= days_l
[vtm
->wday
];
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
);
409 i
= strlen(tp
= days_l
[vtm
->wday
]);
413 case 'h': /* abbreviated month name */
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
);
420 if (vtm
->mon
< 1 || vtm
->mon
> 12)
423 i
= 3, tp
= months_l
[vtm
->mon
-1];
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
);
431 if (vtm
->mon
< 1 || vtm
->mon
> 12)
434 i
= strlen(tp
= months_l
[vtm
->mon
-1]);
437 case 'c': /* appropriate date and time representation */
438 STRFTIME("%a %b %e %H:%M:%S %Y");
441 case 'd': /* day of the month, 01 - 31 */
442 i
= range(1, vtm
->mday
, 31);
443 FMT('0', 2, "d", (int)i
);
446 case 'H': /* hour, 24-hour clock, 00 - 23 */
447 i
= range(0, vtm
->hour
, 23);
448 FMT('0', 2, "d", (int)i
);
451 case 'I': /* hour, 12-hour clock, 01 - 12 */
452 i
= range(0, vtm
->hour
, 23);
457 FMT('0', 2, "d", (int)i
);
460 case 'j': /* day of the year, 001 - 366 */
461 i
= range(1, vtm
->yday
, 366);
462 FMT('0', 3, "d", (int)i
);
465 case 'm': /* month, 01 - 12 */
466 i
= range(1, vtm
->mon
, 12);
467 FMT('0', 2, "d", (int)i
);
470 case 'M': /* minute, 00 - 59 */
471 i
= range(0, vtm
->min
, 59);
472 FMT('0', 2, "d", (int)i
);
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
);
482 i
= range(0, vtm
->hour
, 23);
492 time_t sec
= ts
->tv_sec
;
494 FMT('0', 1, PRI_TIMET_PREFIX
"d", sec
);
496 FMT('0', 1, PRI_TIMET_PREFIX
"u", sec
);
499 VALUE sec
= div(timev
, INT2FIX(1));
500 FMTV('0', 1, "d", sec
);
504 case 'S': /* second, 00 - 60 */
505 i
= range(0, vtm
->sec
, 60);
506 FMT('0', 2, "d", (int)i
);
509 case 'U': /* week of year, Sunday is first day of week */
510 FMT('0', 2, "d", weeknumber_v(vtm
, 0));
513 case 'w': /* weekday, Sunday == 0, 0 - 6 */
514 i
= range(0, vtm
->wday
, 6);
515 FMT('0', 1, "d", (int)i
);
518 case 'W': /* week of year, Monday is first day of week */
519 FMT('0', 2, "d", weeknumber_v(vtm
, 1));
522 case 'x': /* appropriate date representation */
523 STRFTIME("%m/%d/%y");
526 case 'X': /* appropriate time representation */
527 STRFTIME("%H:%M:%S");
530 case 'y': /* year without a century, 00 - 99 */
531 i
= NUM2INT(mod(vtm
->year
, INT2FIX(100)));
532 FMT('0', 2, "d", (int)i
);
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
);
541 FMTV('0', 4, "d", vtm
->year
);
545 #ifdef MAILHEADER_EXT
546 case 'z': /* time zone offset east of GMT e.g. -0600 */
551 off
= NUM2LONG(rb_funcall(vtm
->utc_offset
, rb_intern("round"), 0));
553 if (off
< 0 || (gmt
&& (flags
& BIT_OF(LEFT
)))) {
561 case 0: /* %z -> +hhmm */
562 precision
= precision
<= 5 ? 2 : precision
-3;
563 NEEDS(precision
+ 3);
566 case 1: /* %:z -> +hh:mm */
567 precision
= precision
<= 6 ? 2 : precision
-4;
568 NEEDS(precision
+ 4);
571 case 2: /* %::z -> +hh:mm:ss */
572 precision
= precision
<= 9 ? 2 : precision
-7;
573 NEEDS(precision
+ 7);
576 case 3: /* %:::z -> +hh[:mm[:ss]] */
577 if (off
% 3600 == 0) {
578 precision
= precision
<= 3 ? 2 : precision
-1;
579 NEEDS(precision
+ 3);
581 else if (off
% 60 == 0) {
582 precision
= precision
<= 6 ? 2 : precision
-4;
583 NEEDS(precision
+ 4);
586 precision
= precision
<= 9 ? 2 : precision
-7;
587 NEEDS(precision
+ 9);
595 i
= snprintf(s
, endp
- s
, (padding
== ' ' ? "%+*ld" : "%+.*ld"),
596 precision
+ (padding
== ' '), sign
* (off
/ 3600));
598 if (sign
< 0 && off
< 3600) {
599 *(padding
== ' ' ? s
+ i
- 2 : s
) = '-';
603 if (colons
== 3 && off
== 0)
607 i
= snprintf(s
, endp
- s
, "%02d", (int)(off
/ 60));
611 if (colons
== 3 && off
== 0)
615 i
= snprintf(s
, endp
- s
, "%02d", (int)off
);
620 #endif /* MAILHEADER_EXT */
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
);
632 if (NIL_P(vtm
->zone
)) {
637 zone
= rb_time_zone_abbreviation(vtm
->zone
, time
);
639 tp
= RSTRING_PTR(zone
);
641 for (i
= 0; i
< TBUFSIZE
&& tp
[i
]; i
++) {
642 if ((unsigned char)tp
[i
] > 0x7F) {
643 VALUE str
= rb_str_conv_enc_opts(rb_str_new_cstr(tp
), rb_locale_encoding(), enc
, ECONV_UNDEF_REPLACE
|ECONV_INVALID_REPLACE
, Qnil
);
644 i
= strlcpy(tbuf
, RSTRING_PTR(str
), TBUFSIZE
);
656 case 'n': /* same as \n */
661 case 't': /* same as \t */
666 case 'D': /* date as %m/%d/%y */
667 STRFTIME("%m/%d/%y");
670 case 'e': /* day of month, blank padded */
671 FMT(' ', 2, "d", range(1, vtm
->mday
, 31));
674 case 'r': /* time as %I:%M:%S %p */
675 STRFTIME("%I:%M:%S %p");
678 case 'R': /* time as %H:%M */
682 case 'T': /* time as %H:%M:%S */
683 STRFTIME("%H:%M:%S");
688 case 'k': /* hour, 24-hour clock, blank pad */
689 i
= range(0, vtm
->hour
, 23);
690 FMT(' ', 2, "d", (int)i
);
693 case 'l': /* hour, 12-hour clock, 1 - 12, blank pad */
694 i
= range(0, vtm
->hour
, 23);
699 FMT(' ', 2, "d", (int)i
);
705 case 'v': /* date as dd-bbb-YYYY */
706 STRFTIME("%e-%^b-%4Y");
713 FMTV('0', 2, "d", div(vtm
->year
, INT2FIX(100)));
717 /* POSIX locale extensions, ignored for now */
718 if (!format
[1] || !strchr("cCxXyY", format
[1]))
722 /* POSIX locale extensions, ignored for now */
723 if (!format
[1] || !strchr("deHkIlmMSuUVwWy", format
[1]))
727 case 'V': /* week of year according ISO 8601 */
728 FMT('0', 2, "d", iso8601wknum_v(vtm
));
732 /* ISO 8601: Weekday as a decimal number [1 (Monday) - 7] */
733 FMT('0', 1, "d", vtm
->wday
== 0 ? 7 : vtm
->wday
);
735 #endif /* POSIX2_DATE */
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.
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));
757 if (*format
== 'G') {
759 const long y
= FIX2LONG(yv
);
760 FMT('0', 0 <= y
? 4 : 5, "ld", y
);
763 FMTV('0', 4, "d", yv
);
767 yv
= mod(yv
, INT2FIX(100));
769 FMT('0', 2, "ld", y
);
774 #endif /* ISO_DATE_EXT */
783 * fractional second digits. default is 9 digits
786 * %3N millisecond (3 digits)
787 * %6N microsecond (6 digits)
788 * %9N nanosecond (9 digits)
792 if (precision
<= 0) {
798 long subsec
= ts
->tv_nsec
;
800 snprintf(s
, endp
- s
, "%09ld", subsec
);
801 memset(s
+9, '0', precision
-9);
806 for (i
= 0; i
< 9-precision
; i
++)
808 snprintf(s
, endp
- s
, "%0*ld", precision
, subsec
);
813 VALUE subsec
= mod(timev
, INT2FIX(1));
819 subsec
= mul(subsec
, INT2FIX(1000000000));
826 subsec
= mul(subsec
, INT2FIX(n
));
827 subsec
= div(subsec
, INT2FIX(1));
829 if (FIXNUM_P(subsec
)) {
830 (void)snprintf(s
, endp
- s
, "%0*ld", precision
, FIX2LONG(subsec
));
834 VALUE args
[2], result
;
835 args
[0] = INT2FIX(precision
);
837 result
= rb_str_format(2, args
,
838 rb_fstring_lit("%0*d"));
839 (void)strlcpy(s
, StringValueCStr(result
), endp
-s
);
845 case 'F': /* Equivalent to %Y-%m-%d */
846 STRFTIME("%Y-%m-%d");
851 flags
|= BIT_OF(LEFT
);
852 padding
= precision
= 0;
857 flags
|= BIT_OF(UPPER
);
862 flags
|= BIT_OF(CHCASE
);
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
;
876 format
+= colons
- 1;
881 case '1': case '2': case '3': case '4':
882 case '5': case '6': case '7': case '8': case '9':
886 unsigned long u
= ruby_scan_digits(format
, format_end
-format
, 10, &n
, &ov
);
887 if (ov
|| u
> INT_MAX
) goto unknown
;
906 s
= case_conv(s
, i
, flags
);
909 if (format
!= format_end
) {
913 rb_str_set_len(ftime
, len
);
914 rb_str_resize(ftime
, len
);
922 strftime_size_limit(size_t format_len
)
924 size_t limit
= format_len
* (1*1024*1024);
925 if (limit
< format_len
) limit
= format_len
;
926 else if (limit
< 1024) limit
= 1024;
931 rb_strftime(const char *format
, size_t format_len
, rb_encoding
*enc
,
932 VALUE time
, const struct vtm
*vtm
, VALUE timev
, int gmt
)
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
));
942 rb_strftime_timespec(const char *format
, size_t format_len
, rb_encoding
*enc
,
943 VALUE time
, const struct vtm
*vtm
, struct timespec
*ts
, int gmt
)
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
));
954 rb_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
)
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
);
964 /* isleap --- is a year a leap year? */
969 return ((year
% 4 == 0 && year
% 100 != 0) || year
% 400 == 0);
974 vtm2tm_noyear(const struct vtm
*vtm
, struct tm
*result
)
978 /* for isleap() in iso8601wknum. +100 is -1900 (mod 400). */
979 tm
.tm_year
= FIX2INT(mod(vtm
->year
, INT2FIX(400))) + 100;
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)
992 #if defined(HAVE_TM_ZONE)
999 /* iso8601wknum --- compute week number according to ISO 8601 */
1002 iso8601wknum(const struct tm
*timeptr
)
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.
1011 * ADR: This means if Jan 1 was Monday through Thursday,
1012 * it was week 1, otherwise week 52 or 53.
1014 * XPG4 erroneously included POSIX.2 rationale text in the
1015 * main body of the standard. Thus it requires week 53.
1018 int weeknum
, jan1day
;
1020 /* get week number, Monday as first day of the week */
1021 weeknum
= weeknumber(timeptr
, 1);
1024 * With thanks and tip of the hatlo to tml@tik.vtt.fi
1026 * What day of the week does January 1 fall on?
1028 * (timeptr->tm_yday - jan1.tm_yday) MOD 7 ==
1029 * (timeptr->tm_wday - jan1.tm_wday) MOD 7
1033 * timeptr->tm_wday MOD 7 == timeptr->tm_wday
1034 * from which it follows that. . .
1036 jan1day
= timeptr
->tm_wday
- (timeptr
->tm_yday
% 7);
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.
1045 * What does that mean?
1046 * If Jan 1 was Monday, the week number is exactly right, it can
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.
1054 case 1: /* Monday */
1056 case 2: /* Tuesday */
1057 case 3: /* Wednesday */
1058 case 4: /* Thursday */
1061 case 5: /* Friday */
1062 case 6: /* Saturday */
1063 case 0: /* Sunday */
1065 #ifdef USE_BROKEN_XPG4
1066 /* XPG4 (as of March 1994) says 53 unconditionally */
1069 /* get week number of last week of last year */
1070 struct tm dec31ly
; /* 12/31 last 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
);
1083 if (timeptr
->tm_mon
== 11) {
1085 * The last week of the year
1086 * can be in week 1 of next year.
1089 * This can only happen if
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))
1109 iso8601wknum_v(const struct vtm
*vtm
)
1112 vtm2tm_noyear(vtm
, &tm
);
1113 return iso8601wknum(&tm
);
1118 /* weeknumber --- figure how many weeks into the year */
1120 /* With thanks and tip of the hatlo to ado@elsie.nci.nih.gov */
1123 weeknumber(const struct tm
*timeptr
, int firstweekday
)
1125 int wday
= timeptr
->tm_wday
;
1128 if (firstweekday
== 1) {
1129 if (wday
== 0) /* sunday */
1134 ret
= ((timeptr
->tm_yday
+ 7 - wday
) / 7);
1141 weeknumber_v(const struct vtm
*vtm
, int firstweekday
)
1144 vtm2tm_noyear(vtm
, &tm
);
1145 return weeknumber(&tm
, firstweekday
);
1149 /* ADR --- I'm loathe to mess with ado's code ... */
1151 Date
: Wed
, 24 Apr
91 20:54:08 MDT
1152 From
: Michal Jaegermann
<audfax
!emory
!vm
.ucs
.UAlberta
.CA
!NTOMCZAK
>
1153 To
: arnold@audiofax
.com
1156 in a process of fixing of
strftime() in libraries on Atari ST I grabbed
1157 some pieces of code from your own strftime
. When doing that it came
1158 to mind that your
weeknumber() function compiles a little bit nicer
1159 in the following form
:
1161 * firstweekday is 0 if starting in Sunday, non-zero if in Monday
1164 return (timeptr
->tm_yday
- timeptr
->tm_wday
+
1165 (firstweekday
? (timeptr
->tm_wday
? 8 : 1) : 7)) / 7;
1167 How nicer it depends on a compiler
, of course
, but always a tiny bit
.
1171 ntomczak@vm
.ucs
.ualberta
.ca
1174 #ifdef TEST_STRFTIME
1184 * "tst" is a test driver for the function "strftime".
1191 * Control Data Systems, Inc.
1192 * vogelke@c-17igp.wpafb.af.mil
1198 * cc -o tst -DTEST_STRFTIME strftime.c
1201 /* ADR: I reformatted this to my liking, and deleted some unneeded code. */
1206 #include <sys/time.h>
1212 * Array of time formats.
1215 static char *array
[] =
1217 "(%%A) full weekday name, var length (Sunday..Saturday) %A",
1218 "(%%B) full month name, var length (January..December) %B",
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",
1259 main(int argc
, char **argv
)
1264 char string
[MAXTIME
];
1273 /* Call the function. */
1275 clock
= time((long *) 0);
1276 tm
= localtime(&clock
);
1278 for (k
= 0; next
= array
[k
]; k
++) {
1279 length
= strftime(string
, MAXTIME
, next
, tm
);
1280 printf("%s\n", string
);
1285 #endif /* TEST_STRFTIME */