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