Security fixes
[sxemacs] / src / strftime.c
1 /* strftime - custom formatting of date and/or time
2    Copyright (C) 1989, 1991, 1992 Free Software Foundation, Inc.
3
4 This file is part of SXEmacs
5
6 SXEmacs is free software: you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation, either version 3 of the License, or
9 (at your option) any later version.
10
11 SXEmacs is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with this program.  If not, see <http://www.gnu.org/licenses/>. */
18
19
20 /* Synched up with: FSF 19.30. */
21
22 /* Note: this version of strftime lacks locale support,
23    but it is standalone.
24
25    Performs `%' substitutions similar to those in printf.  Except
26    where noted, substituted fields have a fixed size; numeric fields are
27    padded if necessary.  Padding is with zeros by default; for fields
28    that display a single number, padding can be changed or inhibited by
29    following the `%' with one of the modifiers described below.  Unknown
30    field specifiers are copied as normal characters.  All other
31    characters are copied to the output without change.
32
33    Supports a superset of the ANSI C field specifiers.
34
35    Literal character fields:
36    %    %
37    n    newline
38    t    tab
39
40    Numeric modifiers (a nonstandard extension):
41    -    do not pad the field
42    _    pad the field with spaces
43
44    Time fields:
45    %H   hour (00..23)
46    %I   hour (01..12)
47    %k   hour ( 0..23)
48    %l   hour ( 1..12)
49    %M   minute (00..59)
50    %p   locale's AM or PM
51    %r   time, 12-hour (hh:mm:ss [AP]M)
52    %R   time, 24-hour (hh:mm)
53    %s   time in seconds since 00:00:00, Jan 1, 1970 (a nonstandard extension)
54    %S   second (00..61)
55    %T   time, 24-hour (hh:mm:ss)
56    %X   locale's time representation (%H:%M:%S)
57    %Z   time zone (EDT), or nothing if no time zone is determinable
58
59    Date fields:
60    %a   locale's abbreviated weekday name (Sun..Sat)
61    %A   locale's full weekday name, variable length (Sunday..Saturday)
62    %b   locale's abbreviated month name (Jan..Dec)
63    %B   locale's full month name, variable length (January..December)
64    %c   locale's date and time (Sat Nov 04 12:02:33 EST 1989)
65    %C   century (00..99)
66    %d   day of month (01..31)
67    %e   day of month ( 1..31)
68    %D   date (mm/dd/yy)
69    %h   same as %b
70    %j   day of year (001..366)
71    %m   month (01..12)
72    %U   week number of year with Sunday as first day of week (00..53)
73    %w   day of week (0..6)
74    %W   week number of year with Monday as first day of week (00..53)
75    %x   locale's date representation (mm/dd/yy)
76    %y   last two digits of year (00..99)
77    %Y   year (1970...)
78
79    David MacKenzie <djm@gnu.ai.mit.edu> */
80
81 #ifdef HAVE_CONFIG_H
82 #include <config.h>
83 #include "lisp.h"
84 #endif
85
86 #include <stdio.h>
87 #include <sys/types.h>
88 #if defined(TM_IN_SYS_TIME) || (!defined(HAVE_TM_ZONE) && !defined(HAVE_TZNAME))
89 #include <sys/time.h>
90 #else
91 #include <time.h>
92 #endif
93
94 #ifndef STDC_HEADERS
95 time_t mktime();
96 #endif
97
98 #if defined(HAVE_TZNAME)
99 extern char *tzname[2];
100 #endif
101
102 #ifdef emacs
103 #define strftime emacs_strftime
104 #endif
105
106 /* Types of padding for numbers in date and time. */
107 enum padding {
108         none, blank, zero
109 };
110
111 static char const *const days[] = {
112         "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday",
113             "Saturday"
114 };
115
116 static char const *const months[] = {
117         "January", "February", "March", "April", "May", "June",
118         "July", "August", "September", "October", "November", "December"
119 };
120
121 /* Add character C to STRING and increment LENGTH,
122    unless LENGTH would exceed MAX. */
123
124 #define add_char(c) do          \
125 {                               \
126   if (length + 1 <= max)        \
127     string[length++] = (c);     \
128 } while (0)
129
130 /* Add a 2 digit number to STRING, padding if specified.
131    Return the number of characters added, up to MAX. */
132
133 static int add_num2(char *string, int num, int max, enum padding pad)
134 {
135         int top = num / 10;
136         int length = 0;
137
138         if (top == 0 && pad == blank)
139                 add_char(' ');
140         else if (top != 0 || pad == zero)
141                 add_char(top + '0');
142         add_char(num % 10 + '0');
143         return length;
144 }
145
146 /* Add a 3 digit number to STRING, padding if specified.
147    Return the number of characters added, up to MAX. */
148
149 static int add_num3(char *string, int num, int max, enum padding pad)
150 {
151         int top = num / 100;
152         int mid = (num - top * 100) / 10;
153         int length = 0;
154
155         if (top == 0 && pad == blank)
156                 add_char(' ');
157         else if (top != 0 || pad == zero)
158                 add_char(top + '0');
159         if (mid == 0 && top == 0 && pad == blank)
160                 add_char(' ');
161         else if (mid != 0 || top != 0 || pad == zero)
162                 add_char(mid + '0');
163         add_char(num % 10 + '0');
164         return length;
165 }
166
167 /* Like strncpy except return the number of characters copied. */
168
169 static int add_str(char *to, const char *from, int max)
170 {
171         int i;
172
173         for (i = 0; from[i] && i <= max; ++i)
174                 to[i] = from[i];
175         return i;
176 }
177
178 static int add_num_time_t(char *string, int max, time_t num)
179 {
180         /* This buffer is large enough to hold the character representation
181            (including the trailing NUL) of any unsigned decimal quantity
182            whose binary representation fits in 128 bits.  */
183         char buf[40];
184         int length;
185
186         if (sizeof(num) > 16)
187                 abort();
188         sprintf(buf, "%lu", (unsigned long)num);
189         length = add_str(string, buf, max);
190         return length;
191 }
192
193 /* Return the week in the year of the time in TM, with the weeks
194    starting on Sundays. */
195
196 static int sun_week(const struct tm *tm)
197 {
198         int dl;
199
200         /* Set `dl' to the day in the year of the last day of the week previous
201            to the one containing the day specified in TM.  If the day specified
202            in TM is in the first week of the year, `dl' will be negative or 0.
203            Otherwise, calculate the number of complete weeks before our week
204            (dl / 7) and add any partial week at the start of the year (dl % 7). */
205         dl = tm->tm_yday - tm->tm_wday;
206         return dl <= 0 ? 0 : dl / 7 + (dl % 7 != 0);
207 }
208
209 /* Return the week in the year of the time in TM, with the weeks
210    starting on Mondays. */
211
212 static int mon_week(const struct tm *tm)
213 {
214         int dl, wday;
215
216         if (tm->tm_wday == 0)
217                 wday = 6;
218         else
219                 wday = tm->tm_wday - 1;
220         dl = tm->tm_yday - wday;
221         return dl <= 0 ? 0 : dl / 7 + (dl % 7 != 0);
222 }
223
224 #if !defined(HAVE_TM_ZONE) && !defined(HAVE_TZNAME)
225 char *zone_name(const struct tm *tp);
226 char *zone_name(const struct tm *tp)
227 {
228         char *timezone();
229         struct timeval tv;
230         struct timezone tz;
231
232         gettimeofday(&tv, &tz);
233         return timezone(tz.tz_minuteswest, tp->tm_isdst);
234 }
235 #endif
236
237 /* Format the time given in TM according to FORMAT, and put the
238    results in STRING.
239    Return the number of characters (not including terminating null)
240    that were put into STRING, or 0 if the length would have
241    exceeded MAX. */
242
243 size_t strftime(char *string, size_t max, const char *format,
244                 const struct tm * tm);
245
246 size_t
247 strftime(char *string, size_t max, const char *format, const struct tm *tm)
248 {
249         enum padding pad;       /* Type of padding to apply. */
250         size_t length = 0;      /* Characters put in STRING so far. */
251
252         for (; *format && length < max; ++format) {
253                 if (*format != '%')
254                         add_char(*format);
255                 else {
256                         ++format;
257                         /* Modifiers: */
258                         if (*format == '-') {
259                                 pad = none;
260                                 ++format;
261                         } else if (*format == '_') {
262                                 pad = blank;
263                                 ++format;
264                         } else
265                                 pad = zero;
266
267                         switch (*format) {
268                                 /* Literal character fields: */
269                         case 0:
270                         case '%':
271                                 add_char('%');
272                                 break;
273                         case 'n':
274                                 add_char('\n');
275                                 break;
276                         case 't':
277                                 add_char('\t');
278                                 break;
279                         default:
280                                 add_char(*format);
281                                 break;
282
283                                 /* Time fields: */
284                         case 'H':
285                         case 'k':
286                                 length +=
287                                     add_num2(&string[length], tm->tm_hour,
288                                              max - length,
289                                              *format == 'H' ? pad : blank);
290                                 break;
291                         case 'I':
292                         case 'l':
293                                 {
294                                         int hour12;
295
296                                         if (tm->tm_hour == 0)
297                                                 hour12 = 12;
298                                         else if (tm->tm_hour > 12)
299                                                 hour12 = tm->tm_hour - 12;
300                                         else
301                                                 hour12 = tm->tm_hour;
302                                         length +=
303                                             add_num2(&string[length], hour12,
304                                                      max - length,
305                                                      *format ==
306                                                      'I' ? pad : blank);
307                                 }
308                                 break;
309                         case 'M':
310                                 length +=
311                                     add_num2(&string[length], tm->tm_min,
312                                              max - length, pad);
313                                 break;
314                         case 'p':
315                                 if (tm->tm_hour < 12)
316                                         add_char('A');
317                                 else
318                                         add_char('P');
319                                 add_char('M');
320                                 break;
321                         case 'r':
322                                 length +=
323                                     strftime(&string[length], max - length,
324                                              "%I:%M:%S %p", tm);
325                                 break;
326                         case 'R':
327                                 length +=
328                                     strftime(&string[length], max - length,
329                                              "%H:%M", tm);
330                                 break;
331
332                         case 's':
333                                 {
334                                         struct tm writable_tm;
335                                         writable_tm = *tm;
336                                         length +=
337                                             add_num_time_t(&string[length],
338                                                            max - length,
339                                                            mktime
340                                                            (&writable_tm));
341                                 }
342                                 break;
343
344                         case 'S':
345                                 length +=
346                                     add_num2(&string[length], tm->tm_sec,
347                                              max - length, pad);
348                                 break;
349                         case 'T':
350                                 length +=
351                                     strftime(&string[length], max - length,
352                                              "%H:%M:%S", tm);
353                                 break;
354                         case 'X':
355                                 length +=
356                                     strftime(&string[length], max - length,
357                                              "%H:%M:%S", tm);
358                                 break;
359                         case 'Z':
360 #ifdef HAVE_TM_ZONE
361                                 length +=
362                                     add_str(&string[length], tm->tm_zone,
363                                             max - length);
364 #else
365 #ifdef HAVE_TZNAME
366                                 if (tm->tm_isdst && tzname[1] && *tzname[1])
367                                         length +=
368                                             add_str(&string[length], tzname[1],
369                                                     max - length);
370                                 else
371                                         length +=
372                                             add_str(&string[length], tzname[0],
373                                                     max - length);
374 #else
375                                 length +=
376                                     add_str(&string[length], zone_name(tm),
377                                             max - length);
378 #endif
379 #endif
380                                 break;
381
382                                 /* Date fields: */
383                         case 'a':
384                                 add_char(days[tm->tm_wday][0]);
385                                 add_char(days[tm->tm_wday][1]);
386                                 add_char(days[tm->tm_wday][2]);
387                                 break;
388                         case 'A':
389                                 length +=
390                                     add_str(&string[length], days[tm->tm_wday],
391                                             max - length);
392                                 break;
393                         case 'b':
394                         case 'h':
395                                 add_char(months[tm->tm_mon][0]);
396                                 add_char(months[tm->tm_mon][1]);
397                                 add_char(months[tm->tm_mon][2]);
398                                 break;
399                         case 'B':
400                                 length +=
401                                     add_str(&string[length], months[tm->tm_mon],
402                                             max - length);
403                                 break;
404                         case 'c':
405                                 length +=
406                                     strftime(&string[length], max - length,
407                                              "%a %b %d %H:%M:%S %Z %Y", tm);
408                                 break;
409                         case 'C':
410                                 length +=
411                                     add_num2(&string[length],
412                                              (tm->tm_year + 1900) / 100,
413                                              max - length, pad);
414                                 break;
415                         case 'd':
416                                 length +=
417                                     add_num2(&string[length], tm->tm_mday,
418                                              max - length, pad);
419                                 break;
420                         case 'e':
421                                 length +=
422                                     add_num2(&string[length], tm->tm_mday,
423                                              max - length, blank);
424                                 break;
425                         case 'D':
426                                 length +=
427                                     strftime(&string[length], max - length,
428                                              "%m/%d/%y", tm);
429                                 break;
430                         case 'j':
431                                 length +=
432                                     add_num3(&string[length], tm->tm_yday + 1,
433                                              max - length, pad);
434                                 break;
435                         case 'm':
436                                 length +=
437                                     add_num2(&string[length], tm->tm_mon + 1,
438                                              max - length, pad);
439                                 break;
440                         case 'U':
441                                 length +=
442                                     add_num2(&string[length], sun_week(tm),
443                                              max - length, pad);
444                                 break;
445                         case 'w':
446                                 add_char(tm->tm_wday + '0');
447                                 break;
448                         case 'W':
449                                 length +=
450                                     add_num2(&string[length], mon_week(tm),
451                                              max - length, pad);
452                                 break;
453                         case 'x':
454                                 length +=
455                                     strftime(&string[length], max - length,
456                                              "%m/%d/%y", tm);
457                                 break;
458                         case 'y':
459                                 length +=
460                                     add_num2(&string[length], tm->tm_year % 100,
461                                              max - length, pad);
462                                 break;
463                         case 'Y':
464                                 add_char((tm->tm_year + 1900) / 1000 + '0');
465                                 length +=
466                                     add_num3(&string[length],
467                                              (1900 + tm->tm_year) % 1000,
468                                              max - length, zero);
469                                 break;
470                         }
471                 }
472         }
473         add_char(0);
474         return length - 1;
475 }