fixed Makefile
[platform/upstream/expect.git] / exp_strf.c
1 /* exp_strp.c - functions for exp_timestamp */
2 /*
3  * strftime.c
4  *
5  * Public-domain implementation of ANSI C library routine.
6  *
7  * It's written in old-style C for maximal portability.
8  * However, since I'm used to prototypes, I've included them too.
9  *
10  * If you want stuff in the System V ascftime routine, add the SYSV_EXT define.
11  * For extensions from SunOS, add SUNOS_EXT.
12  * For stuff needed to implement the P1003.2 date command, add POSIX2_DATE.
13  * For VMS dates, add VMS_EXT.
14  * For complete POSIX semantics, add POSIX_SEMANTICS.
15  *
16  * The code for %c, %x, and %X now follows the 1003.2 specification for
17  * the POSIX locale.
18  * This version ignores LOCALE information.
19  * It also doesn't worry about multi-byte characters.
20  * So there.
21  *
22  * This file is also shipped with GAWK (GNU Awk), gawk specific bits of
23  * code are included if GAWK is defined.
24  *
25  * Arnold Robbins <arnold@skeeve.atl.ga.us>
26  * January, February, March, 1991
27  * Updated March, April 1992
28  * Updated April, 1993
29  * Updated February, 1994
30  * Updated May, 1994
31  * Updated January 1995
32  * Updated September 1995
33  *
34  * Fixes from ado@elsie.nci.nih.gov
35  * February 1991, May 1992
36  * Fixes from Tor Lillqvist tml@tik.vtt.fi
37  * May, 1993
38  * Further fixes from ado@elsie.nci.nih.gov
39  * February 1994
40  * %z code from chip@chinacat.unicom.com
41  * Applied September 1995
42  *
43  *
44  * Modified by Don Libes for Expect, 10/93 and 12/95.
45  * Forced POSIX semantics.
46  * Replaced inline/min/max stuff with a single range function.
47  * Removed tzset stuff.
48  * Commented out tzname stuff.
49  *
50  * According to Arnold, the current version of this code can ftp'd from
51  * ftp.mathcs.emory.edu:/pub/arnold/strftime.shar.gz
52  *
53  */
54
55 #include "expect_cf.h"
56 #include "tcl.h"
57
58 #include <stdio.h>
59 #include <ctype.h>
60 #include "string.h"
61
62 /* according to Karl Vogel, time.h is insufficient on Pyramid */
63 /* following is recommended by autoconf */
64
65 #ifdef TIME_WITH_SYS_TIME
66 # include <sys/time.h>
67 # include <time.h>
68 #else
69 # ifdef HAVE_SYS_TIME_H
70 #  include <sys/time.h>
71 # else
72 #  include <time.h>
73 # endif
74 #endif
75
76
77
78 #include <sys/types.h>
79
80 #define SYSV_EXT        1       /* stuff in System V ascftime routine */
81 #define POSIX2_DATE     1       /* stuff in Posix 1003.2 date command */
82
83 #if defined(POSIX2_DATE) && ! defined(SYSV_EXT)
84 #define SYSV_EXT        1
85 #endif
86
87 #if defined(POSIX2_DATE)
88 #define adddecl(stuff)  stuff
89 #else
90 #define adddecl(stuff)
91 #endif
92
93 #ifndef __STDC__
94 #define const
95
96 extern char *getenv();
97 static int weeknumber();
98 adddecl(static int iso8601wknum();)
99 #else
100
101 #ifndef strchr
102 extern char *strchr(const char *str, int ch);
103 #endif
104
105 extern char *getenv(const char *v);
106
107 static int weeknumber(const struct tm *timeptr, int firstweekday);
108 adddecl(static int iso8601wknum(const struct tm *timeptr);)
109 #endif
110
111 /* attempt to use strftime to compute timezone, else fallback to */
112 /* less portable ways */
113 #if !defined(HAVE_STRFTIME)
114 # if defined(HAVE_SV_TIMEZONE)
115 extern char *tzname[2];
116 extern int daylight;
117 # else
118 #  if defined(HAVE_TIMEZONE)
119
120 char           *
121 zone_name (tp)
122 struct tm      *tp;
123 {
124         char           *timezone ();
125         struct timeval  tv;
126         struct timezone tz;
127
128         gettimeofday (&tv, &tz);
129
130         return timezone (tz.tz_minuteswest, tp->tm_isdst);
131 }
132
133 #  endif /* HAVE_TIMEZONE */
134 # endif /* HAVE_SV_TIMEZONE */
135 #endif /* HAVE_STRFTIME */
136
137 static int
138 range(low,item,hi)
139 int low, item, hi;
140 {
141         if (item < low) return low;
142         if (item > hi) return hi;
143         return item;
144 }
145
146 /* strftime --- produce formatted time */
147
148 void
149 /*size_t*/
150 #ifndef __STDC__
151 exp_strftime(/*s,*/ format, timeptr, dstring)
152 /*char *s;*/
153 char *format;
154 const struct tm *timeptr;
155 Tcl_DString *dstring;
156 #else
157 /*exp_strftime(char *s, size_t maxsize, const char *format, const struct tm *timeptr)*/
158 exp_strftime(char *format, const struct tm *timeptr,Tcl_DString *dstring)
159 #endif
160 {
161         int copied;     /* used to suppress copying when called recursively */
162
163 #if 0
164         char *endp = s + maxsize;
165         char *start = s;
166 #endif
167         char *percentptr;
168
169         char tbuf[100];
170         int i;
171
172         /* various tables, useful in North America */
173         static char *days_a[] = {
174                 "Sun", "Mon", "Tue", "Wed",
175                 "Thu", "Fri", "Sat",
176         };
177         static char *days_l[] = {
178                 "Sunday", "Monday", "Tuesday", "Wednesday",
179                 "Thursday", "Friday", "Saturday",
180         };
181         static char *months_a[] = {
182                 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
183                 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
184         };
185         static char *months_l[] = {
186                 "January", "February", "March", "April",
187                 "May", "June", "July", "August", "September",
188                 "October", "November", "December",
189         };
190         static char *ampm[] = { "AM", "PM", };
191
192 /*      for (; *format && s < endp - 1; format++) {*/
193         for (; *format ; format++) {
194                 tbuf[0] = '\0';
195                 copied = 0;             /* has not been copied yet */
196                 percentptr = strchr(format,'%');
197                 if (percentptr == 0) {
198                         Tcl_DStringAppend(dstring,format,-1);
199                         goto out;
200                 } else if (percentptr != format) {
201                         Tcl_DStringAppend(dstring,format,percentptr - format);
202                         format = percentptr;
203                 }
204 #if 0
205                 if (*format != '%') {
206                         *s++ = *format;
207                         continue;
208                 }
209 #endif
210         again:
211                 switch (*++format) {
212                 case '\0':
213                         Tcl_DStringAppend(dstring,"%",1);
214 #if 0
215                         *s++ = '%';
216 #endif
217                         goto out;
218
219                 case '%':
220                         Tcl_DStringAppend(dstring,"%",1);
221                         copied = 1;
222                         break;
223 #if 0
224                         *s++ = '%';
225                         continue;
226 #endif
227
228                 case 'a':       /* abbreviated weekday name */
229                         if (timeptr->tm_wday < 0 || timeptr->tm_wday > 6)
230                                 strcpy(tbuf, "?");
231                         else
232                                 strcpy(tbuf, days_a[timeptr->tm_wday]);
233                         break;
234
235                 case 'A':       /* full weekday name */
236                         if (timeptr->tm_wday < 0 || timeptr->tm_wday > 6)
237                                 strcpy(tbuf, "?");
238                         else
239                                 strcpy(tbuf, days_l[timeptr->tm_wday]);
240                         break;
241
242 #ifdef SYSV_EXT
243                 case 'h':       /* abbreviated month name */
244 #endif
245                 case 'b':       /* abbreviated month name */
246                         if (timeptr->tm_mon < 0 || timeptr->tm_mon > 11)
247                                 strcpy(tbuf, "?");
248                         else
249                                 strcpy(tbuf, months_a[timeptr->tm_mon]);
250                         break;
251
252                 case 'B':       /* full month name */
253                         if (timeptr->tm_mon < 0 || timeptr->tm_mon > 11)
254                                 strcpy(tbuf, "?");
255                         else
256                                 strcpy(tbuf, months_l[timeptr->tm_mon]);
257                         break;
258
259                 case 'c':       /* appropriate date and time representation */
260                         sprintf(tbuf, "%s %s %2d %02d:%02d:%02d %d",
261                                 days_a[range(0, timeptr->tm_wday, 6)],
262                                 months_a[range(0, timeptr->tm_mon, 11)],
263                                 range(1, timeptr->tm_mday, 31),
264                                 range(0, timeptr->tm_hour, 23),
265                                 range(0, timeptr->tm_min, 59),
266                                 range(0, timeptr->tm_sec, 61),
267                                 timeptr->tm_year + 1900);
268                         break;
269
270                 case 'd':       /* day of the month, 01 - 31 */
271                         i = range(1, timeptr->tm_mday, 31);
272                         sprintf(tbuf, "%02d", i);
273                         break;
274
275                 case 'H':       /* hour, 24-hour clock, 00 - 23 */
276                         i = range(0, timeptr->tm_hour, 23);
277                         sprintf(tbuf, "%02d", i);
278                         break;
279
280                 case 'I':       /* hour, 12-hour clock, 01 - 12 */
281                         i = range(0, timeptr->tm_hour, 23);
282                         if (i == 0)
283                                 i = 12;
284                         else if (i > 12)
285                                 i -= 12;
286                         sprintf(tbuf, "%02d", i);
287                         break;
288
289                 case 'j':       /* day of the year, 001 - 366 */
290                         sprintf(tbuf, "%03d", timeptr->tm_yday + 1);
291                         break;
292
293                 case 'm':       /* month, 01 - 12 */
294                         i = range(0, timeptr->tm_mon, 11);
295                         sprintf(tbuf, "%02d", i + 1);
296                         break;
297
298                 case 'M':       /* minute, 00 - 59 */
299                         i = range(0, timeptr->tm_min, 59);
300                         sprintf(tbuf, "%02d", i);
301                         break;
302
303                 case 'p':       /* am or pm based on 12-hour clock */
304                         i = range(0, timeptr->tm_hour, 23);
305                         if (i < 12)
306                                 strcpy(tbuf, ampm[0]);
307                         else
308                                 strcpy(tbuf, ampm[1]);
309                         break;
310
311                 case 'S':       /* second, 00 - 61 */
312                         i = range(0, timeptr->tm_sec, 61);
313                         sprintf(tbuf, "%02d", i);
314                         break;
315
316                 case 'U':       /* week of year, Sunday is first day of week */
317                         sprintf(tbuf, "%02d", weeknumber(timeptr, 0));
318                         break;
319
320                 case 'w':       /* weekday, Sunday == 0, 0 - 6 */
321                         i = range(0, timeptr->tm_wday, 6);
322                         sprintf(tbuf, "%d", i);
323                         break;
324
325                 case 'W':       /* week of year, Monday is first day of week */
326                         sprintf(tbuf, "%02d", weeknumber(timeptr, 1));
327                         break;
328
329                 case 'x':       /* appropriate date representation */
330                         sprintf(tbuf, "%s %s %2d %d",
331                                 days_a[range(0, timeptr->tm_wday, 6)],
332                                 months_a[range(0, timeptr->tm_mon, 11)],
333                                 range(1, timeptr->tm_mday, 31),
334                                 timeptr->tm_year + 1900);
335                         break;
336
337                 case 'X':       /* appropriate time representation */
338                         sprintf(tbuf, "%02d:%02d:%02d",
339                                 range(0, timeptr->tm_hour, 23),
340                                 range(0, timeptr->tm_min, 59),
341                                 range(0, timeptr->tm_sec, 61));
342                         break;
343
344                 case 'y':       /* year without a century, 00 - 99 */
345                         i = timeptr->tm_year % 100;
346                         sprintf(tbuf, "%02d", i);
347                         break;
348
349                 case 'Y':       /* year with century */
350                         sprintf(tbuf, "%d", 1900 + timeptr->tm_year);
351                         break;
352
353                 case 'Z':       /* time zone name or abbrevation */
354 #if defined(HAVE_STRFTIME)
355                         strftime(tbuf,sizeof tbuf,"%Z",timeptr);
356 #else
357 # if defined(HAVE_SV_TIMEZONE)
358                         i = 0;
359                         if (daylight && timeptr->tm_isdst)
360                                 i = 1;
361                         strcpy(tbuf, tzname[i]);
362 # else
363                         strcpy(tbuf, zone_name (timeptr));
364 #  if defined(HAVE_TIMEZONE)
365 #  endif /* HAVE_TIMEZONE */
366                         /* no timezone available */
367                         /* feel free to add others here */
368 # endif /* HAVE_SV_TIMEZONE */
369 #endif /* HAVE STRFTIME */
370                         break;
371
372 #ifdef SYSV_EXT
373                 case 'n':       /* same as \n */
374                         tbuf[0] = '\n';
375                         tbuf[1] = '\0';
376                         break;
377
378                 case 't':       /* same as \t */
379                         tbuf[0] = '\t';
380                         tbuf[1] = '\0';
381                         break;
382
383                 case 'D':       /* date as %m/%d/%y */
384                         exp_strftime("%m/%d/%y", timeptr, dstring);
385                         copied = 1;
386 /*                      exp_strftime(tbuf, sizeof tbuf, "%m/%d/%y", timeptr);*/
387                         break;
388
389                 case 'e':       /* day of month, blank padded */
390                         sprintf(tbuf, "%2d", range(1, timeptr->tm_mday, 31));
391                         break;
392
393                 case 'r':       /* time as %I:%M:%S %p */
394                         exp_strftime("%I:%M:%S %p", timeptr, dstring);
395                         copied = 1;
396 /*                      exp_strftime(tbuf, sizeof tbuf, "%I:%M:%S %p", timeptr);*/
397                         break;
398
399                 case 'R':       /* time as %H:%M */
400                         exp_strftime("%H:%M", timeptr, dstring);
401                         copied = 1;
402 /*                      exp_strftime(tbuf, sizeof tbuf, "%H:%M", timeptr);*/
403                         break;
404
405                 case 'T':       /* time as %H:%M:%S */
406                         exp_strftime("%H:%M:%S", timeptr, dstring);
407                         copied = 1;
408 /*                      exp_strftime(tbuf, sizeof tbuf, "%H:%M:%S", timeptr);*/
409                         break;
410 #endif
411
412 #ifdef POSIX2_DATE
413                 case 'C':
414                         sprintf(tbuf, "%02d", (timeptr->tm_year + 1900) / 100);
415                         break;
416
417
418                 case 'E':
419                 case 'O':
420                         /* POSIX locale extensions, ignored for now */
421                         goto again;
422                 case 'V':       /* week of year according ISO 8601 */
423                         sprintf(tbuf, "%02d", iso8601wknum(timeptr));
424                         break;
425
426                 case 'u':
427                 /* ISO 8601: Weekday as a decimal number [1 (Monday) - 7] */
428                         sprintf(tbuf, "%d", timeptr->tm_wday == 0 ? 7 :
429                                         timeptr->tm_wday);
430                         break;
431 #endif  /* POSIX2_DATE */
432                 default:
433                         tbuf[0] = '%';
434                         tbuf[1] = *format;
435                         tbuf[2] = '\0';
436                         break;
437                 }
438                 if (!copied)
439                         Tcl_DStringAppend(dstring,tbuf,-1);
440 #if 0
441                 i = strlen(tbuf);
442                 if (i) {
443                         if (s + i < endp - 1) {
444                                 strcpy(s, tbuf);
445                                 s += i;
446                         } else
447                                 return 0;
448 #endif
449         }
450 out:;
451 #if 0
452         if (s < endp && *format == '\0') {
453                 *s = '\0';
454                 return (s - start);
455         } else
456                 return 0;
457 #endif
458 }
459
460 /* isleap --- is a year a leap year? */
461
462 #ifndef __STDC__
463 static int
464 isleap(year)
465 int year;
466 #else
467 static int
468 isleap(int year)
469 #endif
470 {
471         return ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0);
472 }
473
474 #ifdef POSIX2_DATE
475 /* iso8601wknum --- compute week number according to ISO 8601 */
476
477 #ifndef __STDC__
478 static int
479 iso8601wknum(timeptr)
480 const struct tm *timeptr;
481 #else
482 static int
483 iso8601wknum(const struct tm *timeptr)
484 #endif
485 {
486         /*
487          * From 1003.2:
488          *      If the week (Monday to Sunday) containing January 1
489          *      has four or more days in the new year, then it is week 1;
490          *      otherwise it is the highest numbered week of the previous
491          *      (52 or 53) year, and the next week is week 1.
492          *
493          * ADR: This means if Jan 1 was Monday through Thursday,
494          *      it was week 1, otherwise week 53.
495          * 
496          * XPG4 erroneously included POSIX.2 rationale text in the
497          * main body of the standard. Thus it requires week 53.
498          */
499
500         int weeknum, jan1day;
501
502         /* get week number, Monday as first day of the week */
503         weeknum = weeknumber(timeptr, 1);
504
505         /*
506          * With thanks and tip of the hatlo to tml@tik.vtt.fi
507          *
508          * What day of the week does January 1 fall on?
509          * We know that
510          *      (timeptr->tm_yday - jan1.tm_yday) MOD 7 ==
511          *              (timeptr->tm_wday - jan1.tm_wday) MOD 7
512          * and that
513          *      jan1.tm_yday == 0
514          * and that
515          *      timeptr->tm_wday MOD 7 == timeptr->tm_wday
516          * from which it follows that. . .
517          */
518         jan1day = timeptr->tm_wday - (timeptr->tm_yday % 7);
519         if (jan1day < 0)
520                 jan1day += 7;
521
522         /*
523          * If Jan 1 was a Monday through Thursday, it was in
524          * week 1.  Otherwise it was last year's highest week, which is
525          * this year's week 0.
526          *
527          * What does that mean?
528          * If Jan 1 was Monday, the week number is exactly right, it can
529          *      never be 0.
530          * If it was Tuesday through Thursday, the weeknumber is one
531          *      less than it should be, so we add one.
532          * Otherwise, Friday, Saturday or Sunday, the week number is
533          * OK, but if it is 0, it needs to be 52 or 53.
534          */
535         switch (jan1day) {
536         case 1:         /* Monday */
537                 break;
538         case 2:         /* Tuesday */
539         case 3:         /* Wednesday */
540         case 4:         /* Thursday */
541                 weeknum++;
542                 break;
543         case 5:         /* Friday */
544         case 6:         /* Saturday */
545         case 0:         /* Sunday */
546                 if (weeknum == 0) {
547 #ifdef USE_BROKEN_XPG4
548                         /* XPG4 (as of March 1994) says 53 unconditionally */
549                         weeknum = 53;
550 #else
551                         /* get week number of last week of last year */
552                         struct tm dec31ly;      /* 12/31 last year */
553                         dec31ly = *timeptr;
554                         dec31ly.tm_year--;
555                         dec31ly.tm_mon = 11;
556                         dec31ly.tm_mday = 31;
557                         dec31ly.tm_wday = (jan1day == 0) ? 6 : jan1day - 1;
558                         dec31ly.tm_yday = 364 + isleap(dec31ly.tm_year + 1900);
559                         weeknum = iso8601wknum(& dec31ly);
560 #endif
561                 }
562                 break;
563         }
564
565         if (timeptr->tm_mon == 11) {
566                 /*
567                  * The last week of the year
568                  * can be in week 1 of next year.
569                  * Sigh.
570                  *
571                  * This can only happen if
572                  *      M   T  W
573                  *      29  30 31
574                  *      30  31
575                  *      31
576                  */
577                 int wday, mday;
578
579                 wday = timeptr->tm_wday;
580                 mday = timeptr->tm_mday;
581                 if (   (wday == 1 && (mday >= 29 && mday <= 31))
582                     || (wday == 2 && (mday == 30 || mday == 31))
583                     || (wday == 3 &&  mday == 31))
584                         weeknum = 1;
585         }
586
587         return weeknum;
588 }
589 #endif
590
591 /* weeknumber --- figure how many weeks into the year */
592
593 /* With thanks and tip of the hatlo to ado@elsie.nci.nih.gov */
594
595 #ifndef __STDC__
596 static int
597 weeknumber(timeptr, firstweekday)
598 const struct tm *timeptr;
599 int firstweekday;
600 #else
601 static int
602 weeknumber(const struct tm *timeptr, int firstweekday)
603 #endif
604 {
605         int wday = timeptr->tm_wday;
606         int ret;
607
608         if (firstweekday == 1) {
609                 if (wday == 0)  /* sunday */
610                         wday = 6;
611                 else
612                         wday--;
613         }
614         ret = ((timeptr->tm_yday + 7 - wday) / 7);
615         if (ret < 0)
616                 ret = 0;
617         return ret;
618 }