XEmacs 21.2.7
[chise/xemacs-chise.git.1] / 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(WINDOWSNT) || defined(__CYGWIN32__)
98 #include <time.h>
99 #else
100 #if defined(HAVE_TZNAME)
101 extern char *tzname[2];
102 #endif
103 #endif /* WINDOWSNT */
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 *
237 zone_name (CONST struct tm *tp)
238 {
239   char *timezone ();
240   struct timeval tv;
241   struct timezone tz;
242
243   gettimeofday (&tv, &tz);
244   return timezone (tz.tz_minuteswest, tp->tm_isdst);
245 }
246 #endif
247
248 /* Format the time given in TM according to FORMAT, and put the
249    results in STRING.
250    Return the number of characters (not including terminating null)
251    that were put into STRING, or 0 if the length would have
252    exceeded MAX. */
253
254 size_t strftime (char *string, size_t max, CONST char *format,
255                  CONST struct tm *tm);
256
257 size_t
258 strftime (char *string, size_t max, CONST char *format, CONST struct tm *tm)
259 {
260   enum padding pad;             /* Type of padding to apply. */
261   size_t length = 0;            /* Characters put in STRING so far. */
262
263   for (; *format && length < max; ++format)
264     {
265       if (*format != '%')
266         add_char (*format);
267       else
268         {
269           ++format;
270           /* Modifiers: */
271           if (*format == '-')
272             {
273               pad = none;
274               ++format;
275             }
276           else if (*format == '_')
277             {
278               pad = blank;
279               ++format;
280             }
281           else
282             pad = zero;
283
284           switch (*format)
285             {
286               /* Literal character fields: */
287             case 0:
288             case '%':
289               add_char ('%');
290               break;
291             case 'n':
292               add_char ('\n');
293               break;
294             case 't':
295               add_char ('\t');
296               break;
297             default:
298               add_char (*format);
299               break;
300
301               /* Time fields: */
302             case 'H':
303             case 'k':
304               length +=
305                 add_num2 (&string[length], tm->tm_hour, max - length,
306                           *format == 'H' ? pad : blank);
307               break;
308             case 'I':
309             case 'l':
310               {
311                 int hour12;
312
313                 if (tm->tm_hour == 0)
314                   hour12 = 12;
315                 else if (tm->tm_hour > 12)
316                   hour12 = tm->tm_hour - 12;
317                 else
318                   hour12 = tm->tm_hour;
319                 length +=
320                   add_num2 (&string[length], hour12, max - length,
321                             *format == 'I' ? pad : blank);
322               }
323               break;
324             case 'M':
325               length +=
326                 add_num2 (&string[length], tm->tm_min, max - length, pad);
327               break;
328             case 'p':
329               if (tm->tm_hour < 12)
330                 add_char ('A');
331               else
332                 add_char ('P');
333               add_char ('M');
334               break;
335             case 'r':
336               length +=
337                 strftime (&string[length], max - length, "%I:%M:%S %p", tm);
338               break;
339             case 'R':
340               length +=
341                 strftime (&string[length], max - length, "%H:%M", tm);
342               break;
343
344             case 's':
345               {
346                 struct tm writable_tm;
347                 writable_tm = *tm;
348                 length += add_num_time_t (&string[length], max - length,
349                                           mktime (&writable_tm));
350               }
351               break;
352
353             case 'S':
354               length +=
355                 add_num2 (&string[length], tm->tm_sec, max - length, pad);
356               break;
357             case 'T':
358               length +=
359                 strftime (&string[length], max - length, "%H:%M:%S", tm);
360               break;
361             case 'X':
362               length +=
363                 strftime (&string[length], max - length, "%H:%M:%S", tm);
364               break;
365             case 'Z':
366 #ifdef HAVE_TM_ZONE
367               length += add_str (&string[length], tm->tm_zone, max - length);
368 #else
369 #ifdef HAVE_TZNAME
370               if (tm->tm_isdst && tzname[1] && *tzname[1])
371                 length += add_str (&string[length], tzname[1], max - length);
372               else
373                 length += add_str (&string[length], tzname[0], max - length);
374 #else
375               length += add_str (&string[length], zone_name (tm), max - length);
376 #endif
377 #endif
378               break;
379
380               /* Date fields: */
381             case 'a':
382               add_char (days[tm->tm_wday][0]);
383               add_char (days[tm->tm_wday][1]);
384               add_char (days[tm->tm_wday][2]);
385               break;
386             case 'A':
387               length +=
388                 add_str (&string[length], days[tm->tm_wday], max - length);
389               break;
390             case 'b':
391             case 'h':
392               add_char (months[tm->tm_mon][0]);
393               add_char (months[tm->tm_mon][1]);
394               add_char (months[tm->tm_mon][2]);
395               break;
396             case 'B':
397               length +=
398                 add_str (&string[length], months[tm->tm_mon], max - length);
399               break;
400             case 'c':
401               length +=
402                 strftime (&string[length], max - length,
403                           "%a %b %d %H:%M:%S %Z %Y", tm);
404               break;
405             case 'C':
406               length +=
407                 add_num2 (&string[length], (tm->tm_year + 1900) / 100,
408                           max - length, pad);
409               break;
410             case 'd':
411               length +=
412                 add_num2 (&string[length], tm->tm_mday, max - length, pad);
413               break;
414             case 'e':
415               length +=
416                 add_num2 (&string[length], tm->tm_mday, max - length, blank);
417               break;
418             case 'D':
419               length +=
420                 strftime (&string[length], max - length, "%m/%d/%y", tm);
421               break;
422             case 'j':
423               length +=
424                 add_num3 (&string[length], tm->tm_yday + 1, max - length, pad);
425               break;
426             case 'm':
427               length +=
428                 add_num2 (&string[length], tm->tm_mon + 1, max - length, pad);
429               break;
430             case 'U':
431               length +=
432                 add_num2 (&string[length], sun_week (tm), max - length, pad);
433               break;
434             case 'w':
435               add_char (tm->tm_wday + '0');
436               break;
437             case 'W':
438               length +=
439                 add_num2 (&string[length], mon_week (tm), max - length, pad);
440               break;
441             case 'x':
442               length +=
443                 strftime (&string[length], max - length, "%m/%d/%y", tm);
444               break;
445             case 'y':
446               length +=
447                 add_num2 (&string[length], tm->tm_year % 100,
448                           max - length, pad);
449               break;
450             case 'Y':
451               add_char ((tm->tm_year + 1900) / 1000 + '0');
452               length +=
453                 add_num3 (&string[length],
454                           (1900 + tm->tm_year) % 1000, max - length, zero);
455               break;
456             }
457         }
458     }
459   add_char (0);
460   return length - 1;
461 }