More warning suppressions
[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         length = snprintf(buf, sizeof(buf), "%lu", (unsigned long)num);
189         assert(length >= 0 && (size_t)length<sizeof(buf));
190         length = add_str(string, buf, max);
191         return length;
192 }
193
194 /* Return the week in the year of the time in TM, with the weeks
195    starting on Sundays. */
196
197 static int sun_week(const struct tm *tm)
198 {
199         int dl;
200
201         /* Set `dl' to the day in the year of the last day of the week previous
202            to the one containing the day specified in TM.  If the day specified
203            in TM is in the first week of the year, `dl' will be negative or 0.
204            Otherwise, calculate the number of complete weeks before our week
205            (dl / 7) and add any partial week at the start of the year (dl % 7). */
206         dl = tm->tm_yday - tm->tm_wday;
207         return dl <= 0 ? 0 : dl / 7 + (dl % 7 != 0);
208 }
209
210 /* Return the week in the year of the time in TM, with the weeks
211    starting on Mondays. */
212
213 static int mon_week(const struct tm *tm)
214 {
215         int dl, wday;
216
217         if (tm->tm_wday == 0)
218                 wday = 6;
219         else
220                 wday = tm->tm_wday - 1;
221         dl = tm->tm_yday - wday;
222         return dl <= 0 ? 0 : dl / 7 + (dl % 7 != 0);
223 }
224
225 #if !defined(HAVE_TM_ZONE) && !defined(HAVE_TZNAME)
226 char *zone_name(const struct tm *tp);
227 char *zone_name(const struct tm *tp)
228 {
229         char *timezone();
230         struct timeval tv;
231         struct timezone tz;
232
233         gettimeofday(&tv, &tz);
234         return timezone(tz.tz_minuteswest, tp->tm_isdst);
235 }
236 #endif
237
238 /* Format the time given in TM according to FORMAT, and put the
239    results in STRING.
240    Return the number of characters (not including terminating null)
241    that were put into STRING, or 0 if the length would have
242    exceeded MAX. */
243
244 size_t strftime(char *string, size_t max, const char *format,
245                 const struct tm * tm);
246
247 size_t
248 strftime(char *string, size_t max, const char *format, const struct tm *tm)
249 {
250         enum padding pad;       /* Type of padding to apply. */
251         size_t length = 0;      /* Characters put in STRING so far. */
252
253         for (; *format && length < max; ++format) {
254                 if (*format != '%')
255                         add_char(*format);
256                 else {
257                         ++format;
258                         /* Modifiers: */
259                         if (*format == '-') {
260                                 pad = none;
261                                 ++format;
262                         } else if (*format == '_') {
263                                 pad = blank;
264                                 ++format;
265                         } else
266                                 pad = zero;
267
268                         switch (*format) {
269                                 /* Literal character fields: */
270                         case 0:
271                         case '%':
272                                 add_char('%');
273                                 break;
274                         case 'n':
275                                 add_char('\n');
276                                 break;
277                         case 't':
278                                 add_char('\t');
279                                 break;
280                         default:
281                                 add_char(*format);
282                                 break;
283
284                                 /* Time fields: */
285                         case 'H':
286                         case 'k':
287                                 length +=
288                                     add_num2(&string[length], tm->tm_hour,
289                                              max - length,
290                                              *format == 'H' ? pad : blank);
291                                 break;
292                         case 'I':
293                         case 'l':
294                                 {
295                                         int hour12;
296
297                                         if (tm->tm_hour == 0)
298                                                 hour12 = 12;
299                                         else if (tm->tm_hour > 12)
300                                                 hour12 = tm->tm_hour - 12;
301                                         else
302                                                 hour12 = tm->tm_hour;
303                                         length +=
304                                             add_num2(&string[length], hour12,
305                                                      max - length,
306                                                      *format ==
307                                                      'I' ? pad : blank);
308                                 }
309                                 break;
310                         case 'M':
311                                 length +=
312                                     add_num2(&string[length], tm->tm_min,
313                                              max - length, pad);
314                                 break;
315                         case 'p':
316                                 if (tm->tm_hour < 12)
317                                         add_char('A');
318                                 else
319                                         add_char('P');
320                                 add_char('M');
321                                 break;
322                         case 'r':
323                                 length +=
324                                     strftime(&string[length], max - length,
325                                              "%I:%M:%S %p", tm);
326                                 break;
327                         case 'R':
328                                 length +=
329                                     strftime(&string[length], max - length,
330                                              "%H:%M", tm);
331                                 break;
332
333                         case 's':
334                                 {
335                                         struct tm writable_tm;
336                                         writable_tm = *tm;
337                                         length +=
338                                             add_num_time_t(&string[length],
339                                                            max - length,
340                                                            mktime
341                                                            (&writable_tm));
342                                 }
343                                 break;
344
345                         case 'S':
346                                 length +=
347                                     add_num2(&string[length], tm->tm_sec,
348                                              max - length, pad);
349                                 break;
350                         case 'T':
351                                 length +=
352                                     strftime(&string[length], max - length,
353                                              "%H:%M:%S", tm);
354                                 break;
355                         case 'X':
356                                 length +=
357                                     strftime(&string[length], max - length,
358                                              "%H:%M:%S", tm);
359                                 break;
360                         case 'Z':
361 #ifdef HAVE_TM_ZONE
362                                 length +=
363                                     add_str(&string[length], tm->tm_zone,
364                                             max - length);
365 #else
366 #ifdef HAVE_TZNAME
367                                 if (tm->tm_isdst && tzname[1] && *tzname[1])
368                                         length +=
369                                             add_str(&string[length], tzname[1],
370                                                     max - length);
371                                 else
372                                         length +=
373                                             add_str(&string[length], tzname[0],
374                                                     max - length);
375 #else
376                                 length +=
377                                     add_str(&string[length], zone_name(tm),
378                                             max - length);
379 #endif
380 #endif
381                                 break;
382
383                                 /* Date fields: */
384                         case 'a':
385                                 add_char(days[tm->tm_wday][0]);
386                                 add_char(days[tm->tm_wday][1]);
387                                 add_char(days[tm->tm_wday][2]);
388                                 break;
389                         case 'A':
390                                 length +=
391                                     add_str(&string[length], days[tm->tm_wday],
392                                             max - length);
393                                 break;
394                         case 'b':
395                         case 'h':
396                                 add_char(months[tm->tm_mon][0]);
397                                 add_char(months[tm->tm_mon][1]);
398                                 add_char(months[tm->tm_mon][2]);
399                                 break;
400                         case 'B':
401                                 length +=
402                                     add_str(&string[length], months[tm->tm_mon],
403                                             max - length);
404                                 break;
405                         case 'c':
406                                 length +=
407                                     strftime(&string[length], max - length,
408                                              "%a %b %d %H:%M:%S %Z %Y", tm);
409                                 break;
410                         case 'C':
411                                 length +=
412                                     add_num2(&string[length],
413                                              (tm->tm_year + 1900) / 100,
414                                              max - length, pad);
415                                 break;
416                         case 'd':
417                                 length +=
418                                     add_num2(&string[length], tm->tm_mday,
419                                              max - length, pad);
420                                 break;
421                         case 'e':
422                                 length +=
423                                     add_num2(&string[length], tm->tm_mday,
424                                              max - length, blank);
425                                 break;
426                         case 'D':
427                                 length +=
428                                     strftime(&string[length], max - length,
429                                              "%m/%d/%y", tm);
430                                 break;
431                         case 'j':
432                                 length +=
433                                     add_num3(&string[length], tm->tm_yday + 1,
434                                              max - length, pad);
435                                 break;
436                         case 'm':
437                                 length +=
438                                     add_num2(&string[length], tm->tm_mon + 1,
439                                              max - length, pad);
440                                 break;
441                         case 'U':
442                                 length +=
443                                     add_num2(&string[length], sun_week(tm),
444                                              max - length, pad);
445                                 break;
446                         case 'w':
447                                 add_char(tm->tm_wday + '0');
448                                 break;
449                         case 'W':
450                                 length +=
451                                     add_num2(&string[length], mon_week(tm),
452                                              max - length, pad);
453                                 break;
454                         case 'x':
455                                 length +=
456                                     strftime(&string[length], max - length,
457                                              "%m/%d/%y", tm);
458                                 break;
459                         case 'y':
460                                 length +=
461                                     add_num2(&string[length], tm->tm_year % 100,
462                                              max - length, pad);
463                                 break;
464                         case 'Y':
465                                 add_char((tm->tm_year + 1900) / 1000 + '0');
466                                 length +=
467                                     add_num3(&string[length],
468                                              (1900 + tm->tm_year) % 1000,
469                                              max - length, zero);
470                                 break;
471                         }
472                 }
473         }
474         add_char(0);
475         return length - 1;
476 }