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