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