blob: 0ababc144f1dce652249599f2d16145cdc37777e [file] [log] [blame]
Jari Aalto31859422009-01-12 13:36:28 +00001/* strftime - formatted time and date to a string */
Jari Aalto7117c2d2002-07-17 14:10:11 +00002/*
3 * Modified slightly by Chet Ramey for inclusion in Bash
4 */
Jari Aalto7117c2d2002-07-17 14:10:11 +00005/*
6 * strftime.c
7 *
8 * Public-domain implementation of ISO C library routine.
9 *
10 * If you can't do prototypes, get GCC.
11 *
12 * The C99 standard now specifies just about all of the formats
13 * that were additional in the earlier versions of this file.
14 *
15 * For extensions from SunOS, add SUNOS_EXT.
16 * For extensions from HP/UX, add HPUX_EXT.
17 * For VMS dates, add VMS_EXT.
18 * For complete POSIX semantics, add POSIX_SEMANTICS.
19 *
20 * The code for %c, %x, and %X follows the C99 specification for
21 * the "C" locale.
22 *
23 * This version ignores LOCALE information.
24 * It also doesn't worry about multi-byte characters.
25 * So there.
26 *
Jari Aalto7117c2d2002-07-17 14:10:11 +000027 * Arnold Robbins
28 * January, February, March, 1991
29 * Updated March, April 1992
30 * Updated April, 1993
31 * Updated February, 1994
32 * Updated May, 1994
33 * Updated January, 1995
34 * Updated September, 1995
35 * Updated January, 1996
36 * Updated July, 1997
37 * Updated October, 1999
38 * Updated September, 2000
Chet Ramey495aee42011-11-22 19:11:26 -050039 * Updated December, 2001
40 * Updated January, 2011
Chet Rameyac50fba2014-02-26 09:36:43 -050041 * Updated April, 2012
Jari Aalto7117c2d2002-07-17 14:10:11 +000042 *
43 * Fixes from ado@elsie.nci.nih.gov,
44 * February 1991, May 1992
45 * Fixes from Tor Lillqvist tml@tik.vtt.fi,
46 * May 1993
47 * Further fixes from ado@elsie.nci.nih.gov,
48 * February 1994
49 * %z code from chip@chinacat.unicom.com,
50 * Applied September 1995
51 * %V code fixed (again) and %G, %g added,
52 * January 1996
53 * %v code fixed, better configuration,
54 * July 1997
55 * Moved to C99 specification.
56 * September 2000
Chet Ramey495aee42011-11-22 19:11:26 -050057 * Fixes from Tanaka Akira <akr@m17n.org>
58 * December 2001
Jari Aalto7117c2d2002-07-17 14:10:11 +000059 */
60#include <config.h>
61
Jari Aalto7117c2d2002-07-17 14:10:11 +000062#include <stdio.h>
63#include <ctype.h>
64#include <time.h>
Chet Ramey495aee42011-11-22 19:11:26 -050065
Jari Aalto7117c2d2002-07-17 14:10:11 +000066#if defined(TM_IN_SYS_TIME)
67#include <sys/types.h>
68#include <sys/time.h>
69#endif
70
71#include <stdlib.h>
72#include <string.h>
73
74/* defaults: season to taste */
75#define SUNOS_EXT 1 /* stuff in SunOS strftime routine */
76#define VMS_EXT 1 /* include %v for VMS date format */
77#define HPUX_EXT 1 /* non-conflicting stuff in HP-UX date */
Jari Aalto7117c2d2002-07-17 14:10:11 +000078#define POSIX_SEMANTICS 1 /* call tzset() if TZ changes */
Chet Rameyac50fba2014-02-26 09:36:43 -050079#define POSIX_2008 1 /* flag and fw for C, F, G, Y formats */
Jari Aalto7117c2d2002-07-17 14:10:11 +000080
81#undef strchr /* avoid AIX weirdness */
82
Jari Aalto95732b42005-12-07 14:08:12 +000083#if defined (SHELL)
84extern char *get_string_value (const char *);
85#endif
86
Jari Aalto7117c2d2002-07-17 14:10:11 +000087extern void tzset(void);
88static int weeknumber(const struct tm *timeptr, int firstweekday);
89static int iso8601wknum(const struct tm *timeptr);
90
Jari Aalto95732b42005-12-07 14:08:12 +000091#ifndef inline
Jari Aalto7117c2d2002-07-17 14:10:11 +000092#ifdef __GNUC__
93#define inline __inline__
94#else
95#define inline /**/
96#endif
Jari Aalto95732b42005-12-07 14:08:12 +000097#endif
Jari Aalto7117c2d2002-07-17 14:10:11 +000098
99#define range(low, item, hi) max(low, min(item, hi))
100
Chet Rameyac50fba2014-02-26 09:36:43 -0500101/* Whew! This stuff is a mess. */
102#if !defined(OS2) && !defined(MSDOS) && !defined(__CYGWIN__) && defined(HAVE_TZNAME)
Jari Aalto7117c2d2002-07-17 14:10:11 +0000103extern char *tzname[2];
104extern int daylight;
Jari Aaltob80f6442004-07-27 13:29:18 +0000105#if defined(SOLARIS) || defined(mips) || defined (M_UNIX)
Jari Aalto7117c2d2002-07-17 14:10:11 +0000106extern long int timezone, altzone;
107#else
Chet Rameyac50fba2014-02-26 09:36:43 -0500108# if defined (HPUX) || defined(__hpux)
Jari Aalto95732b42005-12-07 14:08:12 +0000109extern long int timezone;
110# else
Chet Rameyac50fba2014-02-26 09:36:43 -0500111# if !defined(__CYGWIN__)
Jari Aalto7117c2d2002-07-17 14:10:11 +0000112extern int timezone, altzone;
Chet Rameyac50fba2014-02-26 09:36:43 -0500113# endif
114# endif
115#endif
Jari Aalto7117c2d2002-07-17 14:10:11 +0000116#endif
117
118#undef min /* just in case */
119
120/* min --- return minimum of two numbers */
121
122static inline int
123min(int a, int b)
124{
125 return (a < b ? a : b);
126}
127
128#undef max /* also, just in case */
129
130/* max --- return maximum of two numbers */
131
132static inline int
133max(int a, int b)
134{
135 return (a > b ? a : b);
136}
137
Chet Rameyac50fba2014-02-26 09:36:43 -0500138#ifdef POSIX_2008
139/* iso_8601_2000_year --- format a year per ISO 8601:2000 as in 1003.1 */
140
141static void
142iso_8601_2000_year(char *buf, int year, size_t fw)
143{
144 int extra;
145 char sign = '\0';
146
147 if (year >= -9999 && year <= 9999) {
148 sprintf(buf, "%0*d", (int) fw, year);
149 return;
150 }
151
152 /* now things get weird */
153 if (year > 9999) {
154 sign = '+';
155 } else {
156 sign = '-';
157 year = -year;
158 }
159
160 extra = year / 10000;
161 year %= 10000;
162 sprintf(buf, "%c_%04d_%d", sign, extra, year);
163}
164#endif /* POSIX_2008 */
165
Jari Aalto7117c2d2002-07-17 14:10:11 +0000166/* strftime --- produce formatted time */
167
168size_t
169strftime(char *s, size_t maxsize, const char *format, const struct tm *timeptr)
170{
171 char *endp = s + maxsize;
172 char *start = s;
173 auto char tbuf[100];
174 long off;
Chet Ramey495aee42011-11-22 19:11:26 -0500175 int i, w;
176 long y;
Jari Aalto7117c2d2002-07-17 14:10:11 +0000177 static short first = 1;
178#ifdef POSIX_SEMANTICS
179 static char *savetz = NULL;
180 static int savetzlen = 0;
181 char *tz;
182#endif /* POSIX_SEMANTICS */
183#ifndef HAVE_TM_ZONE
184#ifndef HAVE_TM_NAME
185#ifndef HAVE_TZNAME
Chet Rameyac50fba2014-02-26 09:36:43 -0500186#ifndef __CYGWIN__
Jari Aalto7117c2d2002-07-17 14:10:11 +0000187 extern char *timezone();
188 struct timeval tv;
189 struct timezone zone;
Chet Rameyac50fba2014-02-26 09:36:43 -0500190#endif /* __CYGWIN__ */
Jari Aalto7117c2d2002-07-17 14:10:11 +0000191#endif /* HAVE_TZNAME */
192#endif /* HAVE_TM_NAME */
193#endif /* HAVE_TM_ZONE */
Chet Rameyac50fba2014-02-26 09:36:43 -0500194#ifdef POSIX_2008
195 int pad;
196 size_t fw;
197 char flag;
198#endif /* POSIX_2008 */
Jari Aalto7117c2d2002-07-17 14:10:11 +0000199
200 /* various tables, useful in North America */
201 static const char *days_a[] = {
202 "Sun", "Mon", "Tue", "Wed",
203 "Thu", "Fri", "Sat",
204 };
205 static const char *days_l[] = {
206 "Sunday", "Monday", "Tuesday", "Wednesday",
207 "Thursday", "Friday", "Saturday",
208 };
209 static const char *months_a[] = {
210 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
211 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
212 };
213 static const char *months_l[] = {
214 "January", "February", "March", "April",
215 "May", "June", "July", "August", "September",
216 "October", "November", "December",
217 };
218 static const char *ampm[] = { "AM", "PM", };
219
220 if (s == NULL || format == NULL || timeptr == NULL || maxsize == 0)
221 return 0;
222
223 /* quick check if we even need to bother */
224 if (strchr(format, '%') == NULL && strlen(format) + 1 >= maxsize)
225 return 0;
226
227#ifndef POSIX_SEMANTICS
228 if (first) {
229 tzset();
230 first = 0;
231 }
232#else /* POSIX_SEMANTICS */
233#if defined (SHELL)
234 tz = get_string_value ("TZ");
235#else
236 tz = getenv("TZ");
237#endif
238 if (first) {
239 if (tz != NULL) {
240 int tzlen = strlen(tz);
241
242 savetz = (char *) malloc(tzlen + 1);
243 if (savetz != NULL) {
244 savetzlen = tzlen + 1;
245 strcpy(savetz, tz);
246 }
247 }
248 tzset();
249 first = 0;
250 }
251 /* if we have a saved TZ, and it is different, recapture and reset */
252 if (tz && savetz && (tz[0] != savetz[0] || strcmp(tz, savetz) != 0)) {
253 i = strlen(tz) + 1;
254 if (i > savetzlen) {
255 savetz = (char *) realloc(savetz, i);
256 if (savetz) {
257 savetzlen = i;
258 strcpy(savetz, tz);
259 }
260 } else
261 strcpy(savetz, tz);
262 tzset();
263 }
264#endif /* POSIX_SEMANTICS */
265
266 for (; *format && s < endp - 1; format++) {
267 tbuf[0] = '\0';
268 if (*format != '%') {
269 *s++ = *format;
270 continue;
271 }
Chet Rameyac50fba2014-02-26 09:36:43 -0500272#ifdef POSIX_2008
273 pad = '\0';
274 fw = 0;
275 flag = '\0';
276 switch (*++format) {
277 case '+':
278 flag = '+';
279 /* fall through */
280 case '0':
281 pad = '0';
282 format++;
283 break;
284
285 case '1':
286 case '2':
287 case '3':
288 case '4':
289 case '5':
290 case '6':
291 case '7':
292 case '8':
293 case '9':
294 break;
295
296 default:
297 format--;
298 goto again;
299 }
300 for (; isdigit(*format); format++) {
301 fw = fw * 10 + (*format - '0');
302 }
303 format--;
304#endif /* POSIX_2008 */
305
Jari Aalto7117c2d2002-07-17 14:10:11 +0000306 again:
307 switch (*++format) {
308 case '\0':
309 *s++ = '%';
310 goto out;
311
312 case '%':
313 *s++ = '%';
314 continue;
315
316 case 'a': /* abbreviated weekday name */
317 if (timeptr->tm_wday < 0 || timeptr->tm_wday > 6)
318 strcpy(tbuf, "?");
319 else
320 strcpy(tbuf, days_a[timeptr->tm_wday]);
321 break;
322
323 case 'A': /* full weekday name */
324 if (timeptr->tm_wday < 0 || timeptr->tm_wday > 6)
325 strcpy(tbuf, "?");
326 else
327 strcpy(tbuf, days_l[timeptr->tm_wday]);
328 break;
329
330 case 'b': /* abbreviated month name */
331 short_month:
332 if (timeptr->tm_mon < 0 || timeptr->tm_mon > 11)
333 strcpy(tbuf, "?");
334 else
335 strcpy(tbuf, months_a[timeptr->tm_mon]);
336 break;
337
338 case 'B': /* full month name */
339 if (timeptr->tm_mon < 0 || timeptr->tm_mon > 11)
340 strcpy(tbuf, "?");
341 else
342 strcpy(tbuf, months_l[timeptr->tm_mon]);
343 break;
344
345 case 'c': /* appropriate date and time representation */
346 /*
347 * This used to be:
348 *
349 * strftime(tbuf, sizeof tbuf, "%a %b %e %H:%M:%S %Y", timeptr);
350 *
351 * Now, per the ISO 1999 C standard, it this:
352 */
353 strftime(tbuf, sizeof tbuf, "%A %B %d %T %Y", timeptr);
354 break;
355
356 case 'C':
Chet Rameyac50fba2014-02-26 09:36:43 -0500357#ifdef POSIX_2008
358 if (pad != '\0' && fw > 0) {
359 size_t min_fw = (flag ? 3 : 2);
360
361 fw = max(fw, min_fw);
362 sprintf(tbuf, flag
363 ? "%+0*ld"
364 : "%0*ld", (int) fw,
365 (timeptr->tm_year + 1900L) / 100);
366 } else
367#endif /* POSIX_2008 */
Jari Aalto7117c2d2002-07-17 14:10:11 +0000368 century:
Chet Rameyac50fba2014-02-26 09:36:43 -0500369 sprintf(tbuf, "%02ld", (timeptr->tm_year + 1900L) / 100);
Jari Aalto7117c2d2002-07-17 14:10:11 +0000370 break;
371
372 case 'd': /* day of the month, 01 - 31 */
373 i = range(1, timeptr->tm_mday, 31);
374 sprintf(tbuf, "%02d", i);
375 break;
376
377 case 'D': /* date as %m/%d/%y */
378 strftime(tbuf, sizeof tbuf, "%m/%d/%y", timeptr);
379 break;
380
381 case 'e': /* day of month, blank padded */
382 sprintf(tbuf, "%2d", range(1, timeptr->tm_mday, 31));
383 break;
384
385 case 'E':
386 /* POSIX (now C99) locale extensions, ignored for now */
387 goto again;
388
389 case 'F': /* ISO 8601 date representation */
Chet Rameyac50fba2014-02-26 09:36:43 -0500390 {
391#ifdef POSIX_2008
392 /*
393 * Field width for %F is for the whole thing.
394 * It must be at least 10.
395 */
396 char m_d[10];
397 strftime(m_d, sizeof m_d, "-%m-%d", timeptr);
398 size_t min_fw = 10;
399
400 if (pad != '\0' && fw > 0) {
401 fw = max(fw, min_fw);
402 } else {
403 fw = min_fw;
404 }
405
406 fw -= 6; /* -XX-XX at end are invariant */
407
408 iso_8601_2000_year(tbuf, timeptr->tm_year + 1900, fw);
409 strcat(tbuf, m_d);
410#else
Jari Aalto7117c2d2002-07-17 14:10:11 +0000411 strftime(tbuf, sizeof tbuf, "%Y-%m-%d", timeptr);
Chet Rameyac50fba2014-02-26 09:36:43 -0500412#endif /* POSIX_2008 */
413 }
Jari Aalto7117c2d2002-07-17 14:10:11 +0000414 break;
415
416 case 'g':
417 case 'G':
418 /*
419 * Year of ISO week.
420 *
421 * If it's December but the ISO week number is one,
422 * that week is in next year.
423 * If it's January but the ISO week number is 52 or
424 * 53, that week is in last year.
425 * Otherwise, it's this year.
426 */
427 w = iso8601wknum(timeptr);
428 if (timeptr->tm_mon == 11 && w == 1)
Chet Ramey495aee42011-11-22 19:11:26 -0500429 y = 1900L + timeptr->tm_year + 1;
Jari Aalto7117c2d2002-07-17 14:10:11 +0000430 else if (timeptr->tm_mon == 0 && w >= 52)
Chet Ramey495aee42011-11-22 19:11:26 -0500431 y = 1900L + timeptr->tm_year - 1;
Jari Aalto7117c2d2002-07-17 14:10:11 +0000432 else
Chet Ramey495aee42011-11-22 19:11:26 -0500433 y = 1900L + timeptr->tm_year;
Jari Aalto7117c2d2002-07-17 14:10:11 +0000434
Chet Rameyac50fba2014-02-26 09:36:43 -0500435 if (*format == 'G') {
436#ifdef POSIX_2008
437 if (pad != '\0' && fw > 0) {
438 size_t min_fw = 4;
439
440 fw = max(fw, min_fw);
441 sprintf(tbuf, flag
442 ? "%+0*ld"
443 : "%0*ld", (int) fw,
444 y);
445 } else
446#endif /* POSIX_2008 */
447 sprintf(tbuf, "%ld", y);
448 }
Jari Aalto7117c2d2002-07-17 14:10:11 +0000449 else
Chet Ramey495aee42011-11-22 19:11:26 -0500450 sprintf(tbuf, "%02ld", y % 100);
Jari Aalto7117c2d2002-07-17 14:10:11 +0000451 break;
452
453 case 'h': /* abbreviated month name */
454 goto short_month;
455
456 case 'H': /* hour, 24-hour clock, 00 - 23 */
457 i = range(0, timeptr->tm_hour, 23);
458 sprintf(tbuf, "%02d", i);
459 break;
460
461 case 'I': /* hour, 12-hour clock, 01 - 12 */
462 i = range(0, timeptr->tm_hour, 23);
463 if (i == 0)
464 i = 12;
465 else if (i > 12)
466 i -= 12;
467 sprintf(tbuf, "%02d", i);
468 break;
469
470 case 'j': /* day of the year, 001 - 366 */
471 sprintf(tbuf, "%03d", timeptr->tm_yday + 1);
472 break;
473
474 case 'm': /* month, 01 - 12 */
475 i = range(0, timeptr->tm_mon, 11);
476 sprintf(tbuf, "%02d", i + 1);
477 break;
478
479 case 'M': /* minute, 00 - 59 */
480 i = range(0, timeptr->tm_min, 59);
481 sprintf(tbuf, "%02d", i);
482 break;
483
484 case 'n': /* same as \n */
485 tbuf[0] = '\n';
486 tbuf[1] = '\0';
487 break;
488
489 case 'O':
490 /* POSIX (now C99) locale extensions, ignored for now */
491 goto again;
492
493 case 'p': /* am or pm based on 12-hour clock */
494 i = range(0, timeptr->tm_hour, 23);
495 if (i < 12)
496 strcpy(tbuf, ampm[0]);
497 else
498 strcpy(tbuf, ampm[1]);
499 break;
500
501 case 'r': /* time as %I:%M:%S %p */
502 strftime(tbuf, sizeof tbuf, "%I:%M:%S %p", timeptr);
503 break;
504
505 case 'R': /* time as %H:%M */
506 strftime(tbuf, sizeof tbuf, "%H:%M", timeptr);
507 break;
508
Chet Ramey495aee42011-11-22 19:11:26 -0500509#if defined(HAVE_MKTIME)
Jari Aalto7117c2d2002-07-17 14:10:11 +0000510 case 's': /* time as seconds since the Epoch */
511 {
512 struct tm non_const_timeptr;
513
514 non_const_timeptr = *timeptr;
515 sprintf(tbuf, "%ld", mktime(& non_const_timeptr));
516 break;
517 }
Chet Ramey495aee42011-11-22 19:11:26 -0500518#endif /* defined(HAVE_MKTIME) */
Jari Aalto7117c2d2002-07-17 14:10:11 +0000519
520 case 'S': /* second, 00 - 60 */
521 i = range(0, timeptr->tm_sec, 60);
522 sprintf(tbuf, "%02d", i);
523 break;
524
525 case 't': /* same as \t */
526 tbuf[0] = '\t';
527 tbuf[1] = '\0';
528 break;
529
530 case 'T': /* time as %H:%M:%S */
531 the_time:
532 strftime(tbuf, sizeof tbuf, "%H:%M:%S", timeptr);
533 break;
534
535 case 'u':
536 /* ISO 8601: Weekday as a decimal number [1 (Monday) - 7] */
537 sprintf(tbuf, "%d", timeptr->tm_wday == 0 ? 7 :
538 timeptr->tm_wday);
539 break;
540
541 case 'U': /* week of year, Sunday is first day of week */
542 sprintf(tbuf, "%02d", weeknumber(timeptr, 0));
543 break;
544
545 case 'V': /* week of year according ISO 8601 */
546 sprintf(tbuf, "%02d", iso8601wknum(timeptr));
547 break;
548
549 case 'w': /* weekday, Sunday == 0, 0 - 6 */
550 i = range(0, timeptr->tm_wday, 6);
551 sprintf(tbuf, "%d", i);
552 break;
553
554 case 'W': /* week of year, Monday is first day of week */
555 sprintf(tbuf, "%02d", weeknumber(timeptr, 1));
556 break;
557
558 case 'x': /* appropriate date representation */
559 strftime(tbuf, sizeof tbuf, "%A %B %d %Y", timeptr);
560 break;
561
562 case 'X': /* appropriate time representation */
563 goto the_time;
564 break;
565
566 case 'y': /* year without a century, 00 - 99 */
567 year:
568 i = timeptr->tm_year % 100;
569 sprintf(tbuf, "%02d", i);
570 break;
571
572 case 'Y': /* year with century */
Chet Rameyac50fba2014-02-26 09:36:43 -0500573#ifdef POSIX_2008
574 if (pad != '\0' && fw > 0) {
575 size_t min_fw = 4;
576
577 fw = max(fw, min_fw);
578 sprintf(tbuf, flag
579 ? "%+0*ld"
580 : "%0*ld", (int) fw,
581 1900L + timeptr->tm_year);
582 } else
583#endif /* POSIX_2008 */
Chet Ramey495aee42011-11-22 19:11:26 -0500584 sprintf(tbuf, "%ld", 1900L + timeptr->tm_year);
Jari Aalto7117c2d2002-07-17 14:10:11 +0000585 break;
586
587 /*
588 * From: Chip Rosenthal <chip@chinacat.unicom.com>
589 * Date: Sun, 19 Mar 1995 00:33:29 -0600 (CST)
590 *
591 * Warning: the %z [code] is implemented by inspecting the
592 * timezone name conditional compile settings, and
593 * inferring a method to get timezone offsets. I've tried
594 * this code on a couple of machines, but I don't doubt
595 * there is some system out there that won't like it.
596 * Maybe the easiest thing to do would be to bracket this
597 * with an #ifdef that can turn it off. The %z feature
598 * would be an admittedly obscure one that most folks can
599 * live without, but it would be a great help to those of
600 * us that muck around with various message processors.
601 */
602 case 'z': /* time zone offset east of GMT e.g. -0600 */
Jari Aalto31859422009-01-12 13:36:28 +0000603 if (timeptr->tm_isdst < 0)
604 break;
Jari Aalto7117c2d2002-07-17 14:10:11 +0000605#ifdef HAVE_TM_NAME
606 /*
607 * Systems with tm_name probably have tm_tzadj as
608 * secs west of GMT. Convert to mins east of GMT.
609 */
610 off = -timeptr->tm_tzadj / 60;
611#else /* !HAVE_TM_NAME */
612#ifdef HAVE_TM_ZONE
613 /*
614 * Systems with tm_zone probably have tm_gmtoff as
615 * secs east of GMT. Convert to mins east of GMT.
616 */
617 off = timeptr->tm_gmtoff / 60;
618#else /* !HAVE_TM_ZONE */
619#if HAVE_TZNAME
620 /*
621 * Systems with tzname[] probably have timezone as
622 * secs west of GMT. Convert to mins east of GMT.
623 */
Chet Rameyac50fba2014-02-26 09:36:43 -0500624# if defined(__hpux) || defined (HPUX) || defined(__CYGWIN__)
Jari Aalto95732b42005-12-07 14:08:12 +0000625 off = -timezone / 60;
626# else
Chet Ramey495aee42011-11-22 19:11:26 -0500627 /* ADR: 4 August 2001, fixed this per gazelle@interaccess.com */
Jari Aalto31859422009-01-12 13:36:28 +0000628 off = -(daylight ? altzone : timezone) / 60;
Chet Rameyac50fba2014-02-26 09:36:43 -0500629# endif
Jari Aalto7117c2d2002-07-17 14:10:11 +0000630#else /* !HAVE_TZNAME */
Jari Aalto95732b42005-12-07 14:08:12 +0000631 gettimeofday(& tv, & zone);
Jari Aalto7117c2d2002-07-17 14:10:11 +0000632 off = -zone.tz_minuteswest;
633#endif /* !HAVE_TZNAME */
634#endif /* !HAVE_TM_ZONE */
635#endif /* !HAVE_TM_NAME */
636 if (off < 0) {
637 tbuf[0] = '-';
638 off = -off;
639 } else {
640 tbuf[0] = '+';
641 }
Chet Ramey495aee42011-11-22 19:11:26 -0500642 sprintf(tbuf+1, "%02ld%02ld", off/60, off%60);
Jari Aalto7117c2d2002-07-17 14:10:11 +0000643 break;
644
645 case 'Z': /* time zone name or abbrevation */
646#ifdef HAVE_TZNAME
647 i = (daylight && timeptr->tm_isdst > 0); /* 0 or 1 */
648 strcpy(tbuf, tzname[i]);
649#else
650#ifdef HAVE_TM_ZONE
651 strcpy(tbuf, timeptr->tm_zone);
652#else
653#ifdef HAVE_TM_NAME
654 strcpy(tbuf, timeptr->tm_name);
655#else
656 gettimeofday(& tv, & zone);
657 strcpy(tbuf, timezone(zone.tz_minuteswest,
658 timeptr->tm_isdst > 0));
659#endif /* HAVE_TM_NAME */
660#endif /* HAVE_TM_ZONE */
661#endif /* HAVE_TZNAME */
662 break;
663
664#ifdef SUNOS_EXT
665 case 'k': /* hour, 24-hour clock, blank pad */
666 sprintf(tbuf, "%2d", range(0, timeptr->tm_hour, 23));
667 break;
668
669 case 'l': /* hour, 12-hour clock, 1 - 12, blank pad */
670 i = range(0, timeptr->tm_hour, 23);
671 if (i == 0)
672 i = 12;
673 else if (i > 12)
674 i -= 12;
675 sprintf(tbuf, "%2d", i);
676 break;
677#endif
678
679#ifdef HPUX_EXT
680 case 'N': /* Emperor/Era name */
681 /* this is essentially the same as the century */
682 goto century; /* %C */
683
684 case 'o': /* Emperor/Era year */
685 goto year; /* %y */
686#endif /* HPUX_EXT */
687
688
689#ifdef VMS_EXT
690 case 'v': /* date as dd-bbb-YYYY */
Chet Ramey495aee42011-11-22 19:11:26 -0500691 sprintf(tbuf, "%2d-%3.3s-%4ld",
Jari Aalto7117c2d2002-07-17 14:10:11 +0000692 range(1, timeptr->tm_mday, 31),
693 months_a[range(0, timeptr->tm_mon, 11)],
Chet Ramey495aee42011-11-22 19:11:26 -0500694 timeptr->tm_year + 1900L);
Jari Aalto7117c2d2002-07-17 14:10:11 +0000695 for (i = 3; i < 6; i++)
696 if (islower(tbuf[i]))
697 tbuf[i] = toupper(tbuf[i]);
698 break;
699#endif
700
701 default:
702 tbuf[0] = '%';
703 tbuf[1] = *format;
704 tbuf[2] = '\0';
705 break;
706 }
707 i = strlen(tbuf);
708 if (i) {
709 if (s + i < endp - 1) {
710 strcpy(s, tbuf);
711 s += i;
712 } else
713 return 0;
714 }
715 }
716out:
717 if (s < endp && *format == '\0') {
718 *s = '\0';
719 return (s - start);
720 } else
721 return 0;
722}
723
724/* isleap --- is a year a leap year? */
725
726static int
Chet Ramey495aee42011-11-22 19:11:26 -0500727isleap(long year)
Jari Aalto7117c2d2002-07-17 14:10:11 +0000728{
729 return ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0);
730}
731
732
733/* iso8601wknum --- compute week number according to ISO 8601 */
734
735static int
736iso8601wknum(const struct tm *timeptr)
737{
738 /*
739 * From 1003.2:
740 * If the week (Monday to Sunday) containing January 1
741 * has four or more days in the new year, then it is week 1;
742 * otherwise it is the highest numbered week of the previous
743 * year (52 or 53), and the next week is week 1.
744 *
745 * ADR: This means if Jan 1 was Monday through Thursday,
746 * it was week 1, otherwise week 52 or 53.
747 *
748 * XPG4 erroneously included POSIX.2 rationale text in the
749 * main body of the standard. Thus it requires week 53.
750 */
751
752 int weeknum, jan1day, diff;
753
754 /* get week number, Monday as first day of the week */
755 weeknum = weeknumber(timeptr, 1);
756
757 /*
758 * With thanks and tip of the hatlo to tml@tik.vtt.fi
759 *
760 * What day of the week does January 1 fall on?
761 * We know that
762 * (timeptr->tm_yday - jan1.tm_yday) MOD 7 ==
763 * (timeptr->tm_wday - jan1.tm_wday) MOD 7
764 * and that
765 * jan1.tm_yday == 0
766 * and that
767 * timeptr->tm_wday MOD 7 == timeptr->tm_wday
768 * from which it follows that. . .
769 */
770 jan1day = timeptr->tm_wday - (timeptr->tm_yday % 7);
771 if (jan1day < 0)
772 jan1day += 7;
773
774 /*
775 * If Jan 1 was a Monday through Thursday, it was in
776 * week 1. Otherwise it was last year's highest week, which is
777 * this year's week 0.
778 *
779 * What does that mean?
780 * If Jan 1 was Monday, the week number is exactly right, it can
781 * never be 0.
782 * If it was Tuesday through Thursday, the weeknumber is one
783 * less than it should be, so we add one.
784 * Otherwise, Friday, Saturday or Sunday, the week number is
785 * OK, but if it is 0, it needs to be 52 or 53.
786 */
787 switch (jan1day) {
788 case 1: /* Monday */
789 break;
790 case 2: /* Tuesday */
791 case 3: /* Wednesday */
792 case 4: /* Thursday */
793 weeknum++;
794 break;
795 case 5: /* Friday */
796 case 6: /* Saturday */
797 case 0: /* Sunday */
798 if (weeknum == 0) {
799#ifdef USE_BROKEN_XPG4
800 /* XPG4 (as of March 1994) says 53 unconditionally */
801 weeknum = 53;
802#else
803 /* get week number of last week of last year */
804 struct tm dec31ly; /* 12/31 last year */
805 dec31ly = *timeptr;
806 dec31ly.tm_year--;
807 dec31ly.tm_mon = 11;
808 dec31ly.tm_mday = 31;
809 dec31ly.tm_wday = (jan1day == 0) ? 6 : jan1day - 1;
Chet Ramey495aee42011-11-22 19:11:26 -0500810 dec31ly.tm_yday = 364 + isleap(dec31ly.tm_year + 1900L);
Jari Aalto7117c2d2002-07-17 14:10:11 +0000811 weeknum = iso8601wknum(& dec31ly);
812#endif
813 }
814 break;
815 }
816
817 if (timeptr->tm_mon == 11) {
818 /*
819 * The last week of the year
820 * can be in week 1 of next year.
821 * Sigh.
822 *
823 * This can only happen if
824 * M T W
825 * 29 30 31
826 * 30 31
827 * 31
828 */
829 int wday, mday;
830
831 wday = timeptr->tm_wday;
832 mday = timeptr->tm_mday;
833 if ( (wday == 1 && (mday >= 29 && mday <= 31))
834 || (wday == 2 && (mday == 30 || mday == 31))
835 || (wday == 3 && mday == 31))
836 weeknum = 1;
837 }
838
839 return weeknum;
840}
841
842/* weeknumber --- figure how many weeks into the year */
843
844/* With thanks and tip of the hatlo to ado@elsie.nci.nih.gov */
845
846static int
847weeknumber(const struct tm *timeptr, int firstweekday)
848{
849 int wday = timeptr->tm_wday;
850 int ret;
851
852 if (firstweekday == 1) {
853 if (wday == 0) /* sunday */
854 wday = 6;
855 else
856 wday--;
857 }
858 ret = ((timeptr->tm_yday + 7 - wday) / 7);
859 if (ret < 0)
860 ret = 0;
861 return ret;
862}
863
864#if 0
865/* ADR --- I'm loathe to mess with ado's code ... */
866
867Date: Wed, 24 Apr 91 20:54:08 MDT
868From: Michal Jaegermann <audfax!emory!vm.ucs.UAlberta.CA!NTOMCZAK>
869To: arnold@audiofax.com
870
871Hi Arnold,
872in a process of fixing of strftime() in libraries on Atari ST I grabbed
873some pieces of code from your own strftime. When doing that it came
874to mind that your weeknumber() function compiles a little bit nicer
875in the following form:
876/*
877 * firstweekday is 0 if starting in Sunday, non-zero if in Monday
878 */
879{
880 return (timeptr->tm_yday - timeptr->tm_wday +
881 (firstweekday ? (timeptr->tm_wday ? 8 : 1) : 7)) / 7;
882}
883How nicer it depends on a compiler, of course, but always a tiny bit.
884
885 Cheers,
886 Michal
887 ntomczak@vm.ucs.ualberta.ca
888#endif
889
890#ifdef TEST_STRFTIME
891
892/*
893 * NAME:
894 * tst
895 *
896 * SYNOPSIS:
897 * tst
898 *
899 * DESCRIPTION:
900 * "tst" is a test driver for the function "strftime".
901 *
902 * OPTIONS:
903 * None.
904 *
905 * AUTHOR:
906 * Karl Vogel
907 * Control Data Systems, Inc.
908 * vogelke@c-17igp.wpafb.af.mil
909 *
910 * BUGS:
911 * None noticed yet.
912 *
913 * COMPILE:
914 * cc -o tst -DTEST_STRFTIME strftime.c
915 */
916
917/* ADR: I reformatted this to my liking, and deleted some unneeded code. */
918
919#ifndef NULL
920#include <stdio.h>
921#endif
922#include <sys/time.h>
923#include <string.h>
924
925#define MAXTIME 132
926
927/*
928 * Array of time formats.
929 */
930
931static char *array[] =
932{
933 "(%%A) full weekday name, var length (Sunday..Saturday) %A",
934 "(%%B) full month name, var length (January..December) %B",
935 "(%%C) Century %C",
936 "(%%D) date (%%m/%%d/%%y) %D",
937 "(%%E) Locale extensions (ignored) %E",
938 "(%%F) full month name, var length (January..December) %F",
939 "(%%H) hour (24-hour clock, 00..23) %H",
940 "(%%I) hour (12-hour clock, 01..12) %I",
941 "(%%M) minute (00..59) %M",
942 "(%%N) Emporer/Era Name %N",
943 "(%%O) Locale extensions (ignored) %O",
944 "(%%R) time, 24-hour (%%H:%%M) %R",
945 "(%%S) second (00..60) %S",
946 "(%%T) time, 24-hour (%%H:%%M:%%S) %T",
947 "(%%U) week of year, Sunday as first day of week (00..53) %U",
948 "(%%V) week of year according to ISO 8601 %V",
949 "(%%W) week of year, Monday as first day of week (00..53) %W",
950 "(%%X) appropriate locale time representation (%H:%M:%S) %X",
951 "(%%Y) year with century (1970...) %Y",
952 "(%%Z) timezone (EDT), or blank if timezone not determinable %Z",
953 "(%%a) locale's abbreviated weekday name (Sun..Sat) %a",
954 "(%%b) locale's abbreviated month name (Jan..Dec) %b",
955 "(%%c) full date (Sat Nov 4 12:02:33 1989)%n%t%t%t %c",
956 "(%%d) day of the month (01..31) %d",
957 "(%%e) day of the month, blank-padded ( 1..31) %e",
958 "(%%h) should be same as (%%b) %h",
959 "(%%j) day of the year (001..366) %j",
960 "(%%k) hour, 24-hour clock, blank pad ( 0..23) %k",
961 "(%%l) hour, 12-hour clock, blank pad ( 0..12) %l",
962 "(%%m) month (01..12) %m",
963 "(%%o) Emporer/Era Year %o",
964 "(%%p) locale's AM or PM based on 12-hour clock %p",
965 "(%%r) time, 12-hour (same as %%I:%%M:%%S %%p) %r",
966 "(%%u) ISO 8601: Weekday as decimal number [1 (Monday) - 7] %u",
967 "(%%v) VMS date (dd-bbb-YYYY) %v",
968 "(%%w) day of week (0..6, Sunday == 0) %w",
969 "(%%x) appropriate locale date representation %x",
970 "(%%y) last two digits of year (00..99) %y",
971 "(%%z) timezone offset east of GMT as HHMM (e.g. -0500) %z",
972 (char *) NULL
973};
974
975/* main routine. */
976
977int
978main(argc, argv)
979int argc;
980char **argv;
981{
982 long time();
983
984 char *next;
985 char string[MAXTIME];
986
987 int k;
988 int length;
989
990 struct tm *tm;
991
992 long clock;
993
994 /* Call the function. */
995
996 clock = time((long *) 0);
997 tm = localtime(&clock);
998
999 for (k = 0; next = array[k]; k++) {
1000 length = strftime(string, MAXTIME, next, tm);
1001 printf("%s\n", string);
1002 }
1003
1004 exit(0);
1005}
1006#endif /* TEST_STRFTIME */