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