Bash  5.0-beta2
Bash - Bourne Again shell
strftime.c
Go to the documentation of this file.
1 /* strftime - formatted time and date to a string */
2 /*
3  * Modified slightly by Chet Ramey for inclusion in Bash
4  */
5 /*
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  *
27  * 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
39  * Updated December, 2001
40  * Updated January, 2011
41  * Updated April, 2012
42  *
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
57  * Fixes from Tanaka Akira <akr@m17n.org>
58  * December 2001
59  */
60 #include <config.h>
61 
62 #include <stdio.h>
63 #include <ctype.h>
64 #include <time.h>
65 
66 #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 */
78 #define POSIX_SEMANTICS 1 /* call tzset() if TZ changes */
79 #define POSIX_2008 1 /* flag and fw for C, F, G, Y formats */
80 
81 #undef strchr /* avoid AIX weirdness */
82 
83 #if defined (SHELL)
84 extern char *get_string_value (const char *);
85 #endif
86 
87 extern void tzset(void);
88 static int weeknumber(const struct tm *timeptr, int firstweekday);
89 static int iso8601wknum(const struct tm *timeptr);
90 
91 #ifndef inline
92 #ifdef __GNUC__
93 #define inline __inline__
94 #else
95 #define inline /**/
96 #endif
97 #endif
98 
99 #define range(low, item, hi) max(low, min(item, hi))
100 
101 /* Whew! This stuff is a mess. */
102 #if !defined(OS2) && !defined(MSDOS) && !defined(__CYGWIN__) && defined(HAVE_TZNAME)
103 extern char *tzname[2];
104 extern int daylight;
105 #if defined(SOLARIS) || defined(mips) || defined (M_UNIX)
106 extern long int timezone, altzone;
107 #else
108 # if defined (HPUX) || defined(__hpux)
109 extern long int timezone;
110 # else
111 # if !defined(__CYGWIN__)
112 extern int timezone, altzone;
113 # endif
114 # endif
115 #endif
116 #endif
117 
118 #undef min /* just in case */
119 
120 /* min --- return minimum of two numbers */
121 
122 static inline int
123 min(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 
132 static inline int
133 max(int a, int b)
134 {
135  return (a > b ? a : b);
136 }
137 
138 #ifdef POSIX_2008
139 /* iso_8601_2000_year --- format a year per ISO 8601:2000 as in 1003.1 */
140 
141 static void
142 iso_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 
166 /* strftime --- produce formatted time */
167 
168 size_t
169 strftime(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;
175  int i, w;
176  long y;
177  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
186 #ifndef __CYGWIN__
187  extern char *timezone();
188  struct timeval tv;
189  struct timezone zone;
190 #endif /* __CYGWIN__ */
191 #endif /* HAVE_TZNAME */
192 #endif /* HAVE_TM_NAME */
193 #endif /* HAVE_TM_ZONE */
194 #ifdef POSIX_2008
195  int pad;
196  size_t fw;
197  char flag;
198 #endif /* POSIX_2008 */
199 
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  }
272 #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 
306  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':
357 #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 */
368  century:
369  sprintf(tbuf, "%02ld", (timeptr->tm_year + 1900L) / 100);
370  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 */
390  {
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
411  strftime(tbuf, sizeof tbuf, "%Y-%m-%d", timeptr);
412 #endif /* POSIX_2008 */
413  }
414  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)
429  y = 1900L + timeptr->tm_year + 1;
430  else if (timeptr->tm_mon == 0 && w >= 52)
431  y = 1900L + timeptr->tm_year - 1;
432  else
433  y = 1900L + timeptr->tm_year;
434 
435  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  }
449  else
450  sprintf(tbuf, "%02ld", y % 100);
451  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 
509 #if defined(HAVE_MKTIME)
510  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  }
518 #endif /* defined(HAVE_MKTIME) */
519 
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 */
573 #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 */
584  sprintf(tbuf, "%ld", 1900L + timeptr->tm_year);
585  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 */
603  if (timeptr->tm_isdst < 0)
604  break;
605 #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  */
624 # if defined(__hpux) || defined (HPUX) || defined(__CYGWIN__)
625  off = -timezone / 60;
626 # else
627  /* ADR: 4 August 2001, fixed this per gazelle@interaccess.com */
628  off = -(daylight ? altzone : timezone) / 60;
629 # endif
630 #else /* !HAVE_TZNAME */
631  gettimeofday(& tv, & zone);
632  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  }
642  sprintf(tbuf+1, "%02ld%02ld", off/60, off%60);
643  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 */
691  sprintf(tbuf, "%2d-%3.3s-%4ld",
692  range(1, timeptr->tm_mday, 31),
693  months_a[range(0, timeptr->tm_mon, 11)],
694  timeptr->tm_year + 1900L);
695  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  }
716 out:
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 
726 static int
727 isleap(long year)
728 {
729  return ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0);
730 }
731 
732 
733 /* iso8601wknum --- compute week number according to ISO 8601 */
734 
735 static int
736 iso8601wknum(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;
810  dec31ly.tm_yday = 364 + isleap(dec31ly.tm_year + 1900L);
811  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 
846 static int
847 weeknumber(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 
867 Date: Wed, 24 Apr 91 20:54:08 MDT
868 From: Michal Jaegermann <audfax!emory!vm.ucs.UAlberta.CA!NTOMCZAK>
869 To: arnold@audiofax.com
870 
871 Hi Arnold,
872 in a process of fixing of strftime() in libraries on Atari ST I grabbed
873 some pieces of code from your own strftime. When doing that it came
874 to mind that your weeknumber() function compiles a little bit nicer
875 in 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 }
883 How 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 
931 static 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 
977 int
978 main(argc, argv)
979 int argc;
980 char **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 */
#define range(low, item, hi)
Definition: strftime.c:99
static char ** argv
Definition: test.c:110
static int min(int a, int b)
Definition: strftime.c:123
#define getenv
Definition: os2compat.h:45
Definition: array.h:32
static nls_uint32 nls_uint32 i
Definition: gettextP.h:74
Definition: jobs.h:60
static void iso_8601_2000_year(char *buf, int year, size_t fw)
Definition: strftime.c:142
char * strchr()
void tzset(void)
char * strcpy()
void exit()
char * realloc()
time_t mktime(struct tm *tp)
Definition: mktime.c:175
static int argc
Definition: test.c:109
#define NULL
Definition: general.h:53
size_t strftime(char *s, size_t maxsize, const char *format, const struct tm *timeptr)
Definition: strftime.c:169
static int max(int a, int b)
Definition: strftime.c:133
int main(int argc, char **argv)
static int isleap(long year)
Definition: strftime.c:727
static int weeknumber(const struct tm *timeptr, int firstweekday)
Definition: strftime.c:847
static int iso8601wknum(const struct tm *timeptr)
Definition: strftime.c:736
#define L(CS)
Definition: glob.c:135
#define malloc
Definition: alloca.c:79
char * get_string_value(char *var_name) const
Definition: variables.c:2492