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