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