merge with 1.9.2b
[gnulib.git] / 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 HAVE_CONFIG_H
78 #if defined (CONFIG_BROKETS)
79 /* We use <config.h> instead of "config.h" so that a compilation
80    using -I. -I$srcdir will use ./config.h rather than $srcdir/config.h
81    (which it would do because it found this file in $srcdir).  */
82 #include <config.h>
83 #else
84 #include "config.h"
85 #endif
86 #endif
87
88 #include <stdio.h>
89 #include <sys/types.h>
90 #if defined(TM_IN_SYS_TIME) || (!defined(HAVE_TM_ZONE) && !defined(HAVE_TZNAME))
91 #include <sys/time.h>
92 #else
93 #include <time.h>
94 #endif
95
96 #ifndef STDC_HEADERS
97 time_t mktime ();
98 #endif
99
100 #if defined(HAVE_TZNAME)
101 extern char *tzname[2];
102 #endif
103
104 /* Types of padding for numbers in date and time. */
105 enum padding
106 {
107   none, blank, zero
108 };
109
110 static char const* const days[] =
111 {
112   "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"
113 };
114
115 static char const * const months[] =
116 {
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)                                                     \
125   do                                                                    \
126     {                                                                   \
127       if (length + 1 <= max)                                            \
128         string[length++] = (c);                                         \
129     }                                                                   \
130   while (0)
131
132 /* Add a 2 digit number to STRING, padding if specified.
133    Return the number of characters added, up to MAX. */
134
135 static int
136 add_num2 (string, num, max, pad)
137      char *string;
138      int num;
139      int max;
140      enum padding pad;
141 {
142   int top = num / 10;
143   int length = 0;
144
145   if (top == 0 && pad == blank)
146     add_char (' ');
147   else if (top != 0 || pad == zero)
148     add_char (top + '0');
149   add_char (num % 10 + '0');
150   return length;
151 }
152
153 /* Add a 3 digit number to STRING, padding if specified.
154    Return the number of characters added, up to MAX. */
155
156 static int
157 add_num3 (string, num, max, pad)
158      char *string;
159      int num;
160      int max;
161      enum padding pad;
162 {
163   int top = num / 100;
164   int mid = (num - top * 100) / 10;
165   int length = 0;
166
167   if (top == 0 && pad == blank)
168     add_char (' ');
169   else if (top != 0 || pad == zero)
170     add_char (top + '0');
171   if (mid == 0 && top == 0 && pad == blank)
172     add_char (' ');
173   else if (mid != 0 || top != 0 || pad == zero)
174     add_char (mid + '0');
175   add_char (num % 10 + '0');
176   return length;
177 }
178
179 /* Like strncpy except return the number of characters copied. */
180
181 static int
182 add_str (to, from, max)
183      char *to;
184      const char *from;
185      int max;
186 {
187   int i;
188
189   for (i = 0; from[i] && i <= max; ++i)
190     to[i] = from[i];
191   return i;
192 }
193
194 static int
195 add_num_time_t (string, max, num)
196      char *string;
197      int max;
198      time_t num;
199 {
200   /* This buffer is large enough to hold the character representation
201      (including the trailing NUL) of any unsigned decimal quantity
202      whose binary representation fits in 128 bits.  */
203   char buf[40];
204   int length;
205
206   if (sizeof (num) > 16)
207     abort ();
208   sprintf (buf, "%lu", (unsigned long) num);
209   length = add_str (string, buf, max);
210   return length;
211 }
212
213 /* Return the week in the year of the time in TM, with the weeks
214    starting on Sundays. */
215
216 static int
217 sun_week (tm)
218      struct tm *tm;
219 {
220   int dl;
221
222   /* Set `dl' to the day in the year of the last day of the week previous
223      to the one containing the day specified in TM.  If the day specified
224      in TM is in the first week of the year, `dl' will be negative or 0.
225      Otherwise, calculate the number of complete weeks before our week
226      (dl / 7) and add any partial week at the start of the year (dl % 7). */
227   dl = tm->tm_yday - tm->tm_wday;
228   return dl <= 0 ? 0 : dl / 7 + (dl % 7 != 0);
229 }
230
231 /* Return the week in the year of the time in TM, with the weeks
232    starting on Mondays. */
233
234 static int
235 mon_week (tm)
236      struct tm *tm;
237 {
238   int dl, wday;
239
240   if (tm->tm_wday == 0)
241     wday = 6;
242   else
243     wday = tm->tm_wday - 1;
244   dl = tm->tm_yday - wday;
245   return dl <= 0 ? 0 : dl / 7 + (dl % 7 != 0);
246 }
247
248 #if !defined(HAVE_TM_ZONE) && !defined(HAVE_TZNAME)
249 char *
250 zone_name (tp)
251      struct tm *tp;
252 {
253   char *timezone ();
254   struct timeval tv;
255   struct timezone tz;
256
257   gettimeofday (&tv, &tz);
258   return timezone (tz.tz_minuteswest, tp->tm_isdst);
259 }
260 #endif
261
262 /* Format the time given in TM according to FORMAT, and put the
263    results in STRING.
264    Return the number of characters (not including terminating null)
265    that were put into STRING, or 0 if the length would have
266    exceeded MAX. */
267
268 size_t
269 strftime (string, max, format, tm)
270      char *string;
271      size_t max;
272      const char *format;
273      const struct tm *tm;
274 {
275   enum padding pad;             /* Type of padding to apply. */
276   size_t length = 0;            /* Characters put in STRING so far. */
277
278   for (; *format && length < max; ++format)
279     {
280       if (*format != '%')
281         add_char (*format);
282       else
283         {
284           ++format;
285           /* Modifiers: */
286           if (*format == '-')
287             {
288               pad = none;
289               ++format;
290             }
291           else if (*format == '_')
292             {
293               pad = blank;
294               ++format;
295             }
296           else
297             pad = zero;
298
299           switch (*format)
300             {
301               /* Literal character fields: */
302             case 0:
303             case '%':
304               add_char ('%');
305               break;
306             case 'n':
307               add_char ('\n');
308               break;
309             case 't':
310               add_char ('\t');
311               break;
312             default:
313               add_char (*format);
314               break;
315
316               /* Time fields: */
317             case 'H':
318             case 'k':
319               length +=
320                 add_num2 (&string[length], tm->tm_hour, max - length,
321                           *format == 'H' ? pad : blank);
322               break;
323             case 'I':
324             case 'l':
325               {
326                 int hour12;
327
328                 if (tm->tm_hour == 0)
329                   hour12 = 12;
330                 else if (tm->tm_hour > 12)
331                   hour12 = tm->tm_hour - 12;
332                 else
333                   hour12 = tm->tm_hour;
334                 length +=
335                   add_num2 (&string[length], hour12, max - length,
336                             *format == 'I' ? pad : blank);
337               }
338               break;
339             case 'M':
340               length +=
341                 add_num2 (&string[length], tm->tm_min, max - length, pad);
342               break;
343             case 'p':
344               if (tm->tm_hour < 12)
345                 add_char ('A');
346               else
347                 add_char ('P');
348               add_char ('M');
349               break;
350             case 'r':
351               length +=
352                 strftime (&string[length], max - length, "%I:%M:%S %p", tm);
353               break;
354             case 'R':
355               length +=
356                 strftime (&string[length], max - length, "%H:%M", tm);
357               break;
358
359             case 's':
360               {
361                 struct tm writable_tm;
362                 writable_tm = *tm;
363                 length += add_num_time_t (&string[length], max - length,
364                                           mktime (&writable_tm));
365               }
366               break;
367
368             case 'S':
369               length +=
370                 add_num2 (&string[length], tm->tm_sec, max - length, pad);
371               break;
372             case 'T':
373               length +=
374                 strftime (&string[length], max - length, "%H:%M:%S", tm);
375               break;
376             case 'X':
377               length +=
378                 strftime (&string[length], max - length, "%H:%M:%S", tm);
379               break;
380             case 'Z':
381 #ifdef HAVE_TM_ZONE
382               length += add_str (&string[length], tm->tm_zone, max - length);
383 #else
384 #ifdef HAVE_TZNAME
385               if (tm->tm_isdst && tzname[1] && *tzname[1])
386                 length += add_str (&string[length], tzname[1], max - length);
387               else
388                 length += add_str (&string[length], tzname[0], max - length);
389 #else
390               length += add_str (&string[length], zone_name (tm), max - length);
391 #endif
392 #endif
393               break;
394
395               /* Date fields: */
396             case 'a':
397               add_char (days[tm->tm_wday][0]);
398               add_char (days[tm->tm_wday][1]);
399               add_char (days[tm->tm_wday][2]);
400               break;
401             case 'A':
402               length +=
403                 add_str (&string[length], days[tm->tm_wday], max - length);
404               break;
405             case 'b':
406             case 'h':
407               add_char (months[tm->tm_mon][0]);
408               add_char (months[tm->tm_mon][1]);
409               add_char (months[tm->tm_mon][2]);
410               break;
411             case 'B':
412               length +=
413                 add_str (&string[length], months[tm->tm_mon], max - length);
414               break;
415             case 'c':
416               length +=
417                 strftime (&string[length], max - length,
418                           "%a %b %d %H:%M:%S %Z %Y", tm);
419               break;
420             case 'C':
421               length +=
422                 add_num2 (&string[length], (tm->tm_year + 1900) / 100,
423                           max - length, pad);
424               break;
425             case 'd':
426               length +=
427                 add_num2 (&string[length], tm->tm_mday, max - length, pad);
428               break;
429             case 'e':
430               length +=
431                 add_num2 (&string[length], tm->tm_mday, max - length, blank);
432               break;
433             case 'D':
434               length +=
435                 strftime (&string[length], max - length, "%m/%d/%y", tm);
436               break;
437             case 'j':
438               length +=
439                 add_num3 (&string[length], tm->tm_yday + 1, max - length, pad);
440               break;
441             case 'm':
442               length +=
443                 add_num2 (&string[length], tm->tm_mon + 1, max - length, pad);
444               break;
445             case 'U':
446               length +=
447                 add_num2 (&string[length], sun_week (tm), max - length, pad);
448               break;
449             case 'w':
450               add_char (tm->tm_wday + '0');
451               break;
452             case 'W':
453               length +=
454                 add_num2 (&string[length], mon_week (tm), max - length, pad);
455               break;
456             case 'x':
457               length +=
458                 strftime (&string[length], max - length, "%m/%d/%y", tm);
459               break;
460             case 'y':
461               length +=
462                 add_num2 (&string[length], tm->tm_year % 100,
463                           max - length, pad);
464               break;
465             case 'Y':
466               add_char ((tm->tm_year + 1900) / 1000 + '0');
467               length +=
468                 add_num3 (&string[length],
469                           (1900 + tm->tm_year) % 1000, max - length, zero);
470               break;
471             }
472         }
473     }
474   add_char (0);
475   return length - 1;
476 }