.
[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   RFC-822 style numeric timezone (-0500) (a nonstandard extension)
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 #include <config.h>
80 #endif
81
82 #include <stdio.h>
83 #include <sys/types.h>
84 #if defined(TM_IN_SYS_TIME) || (!defined(HAVE_TM_ZONE) && !defined(HAVE_TZNAME))
85 #include <sys/time.h>
86 #else
87 #include <time.h>
88 #endif
89
90 #ifndef STDC_HEADERS
91 time_t mktime ();
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      const 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 static int
189 add_num_time_t (string, max, num)
190      char *string;
191      int max;
192      time_t num;
193 {
194   /* This buffer is large enough to hold the character representation
195      (including the trailing NUL) of any unsigned decimal quantity
196      whose binary representation fits in 128 bits.  */
197   char buf[40];
198   int length;
199
200   if (sizeof (num) > 16)
201     abort ();
202   sprintf (buf, "%lu", (unsigned long) num);
203   length = add_str (string, buf, max);
204   return length;
205 }
206
207 /* Convert MINUTES_EAST into a string suitable for use as the RFC-822
208    timezone indicator.  Write no more than MAX bytes into STRING.
209     Return the number of bytes written into STRING.  */
210
211 static int
212 add_num_tz (string, max, minutes_east)
213      char *string;
214      int max;
215      int minutes_east;
216 {
217   int length;
218
219   if (max < 1)
220     return 0;
221
222   if (minutes_east < 0)
223     {
224       *string = '-';
225       minutes_east = -minutes_east;
226     }
227   else
228     *string = '+';
229
230   length = 1 + add_num2 (&string[1], (minutes_east / 60) % 24, max - 1, zero);
231   length += add_num2 (&string[length], minutes_east % 60, max - length, zero);
232
233   return length;
234 }
235
236 /* Return the week in the year of the time in TM, with the weeks
237    starting on Sundays. */
238
239 static int
240 sun_week (tm)
241      struct tm *tm;
242 {
243   int dl;
244
245   /* Set `dl' to the day in the year of the last day of the week previous
246      to the one containing the day specified in TM.  If the day specified
247      in TM is in the first week of the year, `dl' will be negative or 0.
248      Otherwise, calculate the number of complete weeks before our week
249      (dl / 7) and add any partial week at the start of the year (dl % 7). */
250   dl = tm->tm_yday - tm->tm_wday;
251   return dl <= 0 ? 0 : dl / 7 + (dl % 7 != 0);
252 }
253
254 /* Return the week in the year of the time in TM, with the weeks
255    starting on Mondays. */
256
257 static int
258 mon_week (tm)
259      struct tm *tm;
260 {
261   int dl, wday;
262
263   if (tm->tm_wday == 0)
264     wday = 6;
265   else
266     wday = tm->tm_wday - 1;
267   dl = tm->tm_yday - wday;
268   return dl <= 0 ? 0 : dl / 7 + (dl % 7 != 0);
269 }
270
271 #if !defined(HAVE_TM_ZONE) && !defined(HAVE_TZNAME)
272 char *
273 zone_name (tp)
274      struct tm *tp;
275 {
276   char *timezone ();
277   struct timeval tv;
278   struct timezone tz;
279
280   gettimeofday (&tv, &tz);
281   return timezone (tz.tz_minuteswest, tp->tm_isdst);
282 }
283 #endif
284
285 /* Format the time given in TM according to FORMAT, and put the
286    results in STRING.
287    Return the number of characters (not including terminating null)
288    that were put into STRING, or 0 if the length would have
289    exceeded MAX. */
290
291 size_t
292 strftime (string, max, format, tm)
293      char *string;
294      size_t max;
295      const char *format;
296      const struct tm *tm;
297 {
298   enum padding pad;             /* Type of padding to apply. */
299   size_t length = 0;            /* Characters put in STRING so far. */
300
301   for (; *format && length < max; ++format)
302     {
303       if (*format != '%')
304         add_char (*format);
305       else
306         {
307           ++format;
308           /* Modifiers: */
309           if (*format == '-')
310             {
311               pad = none;
312               ++format;
313             }
314           else if (*format == '_')
315             {
316               pad = blank;
317               ++format;
318             }
319           else
320             pad = zero;
321
322           switch (*format)
323             {
324               /* Literal character fields: */
325             case 0:
326             case '%':
327               add_char ('%');
328               break;
329             case 'n':
330               add_char ('\n');
331               break;
332             case 't':
333               add_char ('\t');
334               break;
335             default:
336               add_char (*format);
337               break;
338
339               /* Time fields: */
340             case 'H':
341             case 'k':
342               length +=
343                 add_num2 (&string[length], tm->tm_hour, max - length,
344                           *format == 'H' ? pad : blank);
345               break;
346             case 'I':
347             case 'l':
348               {
349                 int hour12;
350
351                 if (tm->tm_hour == 0)
352                   hour12 = 12;
353                 else if (tm->tm_hour > 12)
354                   hour12 = tm->tm_hour - 12;
355                 else
356                   hour12 = tm->tm_hour;
357                 length +=
358                   add_num2 (&string[length], hour12, max - length,
359                             *format == 'I' ? pad : blank);
360               }
361               break;
362             case 'M':
363               length +=
364                 add_num2 (&string[length], tm->tm_min, max - length, pad);
365               break;
366             case 'p':
367               if (tm->tm_hour < 12)
368                 add_char ('A');
369               else
370                 add_char ('P');
371               add_char ('M');
372               break;
373             case 'r':
374               length +=
375                 strftime (&string[length], max - length, "%I:%M:%S %p", tm);
376               break;
377             case 'R':
378               length +=
379                 strftime (&string[length], max - length, "%H:%M", tm);
380               break;
381
382             case 's':
383               {
384                 struct tm writable_tm;
385                 writable_tm = *tm;
386                 length += add_num_time_t (&string[length], max - length,
387                                           mktime (&writable_tm));
388               }
389               break;
390
391             case 'S':
392               length +=
393                 add_num2 (&string[length], tm->tm_sec, max - length, pad);
394               break;
395             case 'T':
396               length +=
397                 strftime (&string[length], max - length, "%H:%M:%S", tm);
398               break;
399             case 'X':
400               length +=
401                 strftime (&string[length], max - length, "%H:%M:%S", tm);
402               break;
403             case 'z':
404               {
405                 time_t t;
406                 struct tm tml, tmg;
407                 int diff;
408
409                 tml = *tm;
410                 t = mktime (&tml);
411                 tml = *localtime (&t); /* Canonicalize the local time */
412                 tmg = *gmtime (&t);
413
414                 /* Compute the difference */
415
416                 diff = tml.tm_min - tmg.tm_min;
417                 diff += 60 * (tml.tm_hour - tmg.tm_hour);
418
419                 if (tml.tm_mon != tmg.tm_mon)
420                   {
421                     /* We assume no timezone differs from UTC by more than
422                        +- 23 hours.  This should be safe. */
423                     if (tmg.tm_mday == 1)
424                       tml.tm_mday = 0;
425                     else /* tml.tm_mday == 1 */
426                       tmg.tm_mday = 0;
427                   }
428
429                 diff += 1440 * (tml.tm_mday - tmg.tm_mday);
430
431                 length += add_num_tz (&string[length], max - length, diff);
432               }
433               break;
434             case 'Z':
435 #ifdef HAVE_TM_ZONE
436               length += add_str (&string[length], tm->tm_zone, max - length);
437 #else
438 #ifdef HAVE_TZNAME
439               if (tm->tm_isdst && tzname[1] && *tzname[1])
440                 length += add_str (&string[length], tzname[1], max - length);
441               else
442                 length += add_str (&string[length], tzname[0], max - length);
443 #else
444               length += add_str (&string[length], zone_name (tm), max - length);
445 #endif
446 #endif
447               break;
448
449               /* Date fields: */
450             case 'a':
451               add_char (days[tm->tm_wday][0]);
452               add_char (days[tm->tm_wday][1]);
453               add_char (days[tm->tm_wday][2]);
454               break;
455             case 'A':
456               length +=
457                 add_str (&string[length], days[tm->tm_wday], max - length);
458               break;
459             case 'b':
460             case 'h':
461               add_char (months[tm->tm_mon][0]);
462               add_char (months[tm->tm_mon][1]);
463               add_char (months[tm->tm_mon][2]);
464               break;
465             case 'B':
466               length +=
467                 add_str (&string[length], months[tm->tm_mon], max - length);
468               break;
469             case 'c':
470               length +=
471                 strftime (&string[length], max - length,
472                           "%a %b %d %H:%M:%S %Z %Y", tm);
473               break;
474             case 'C':
475               length +=
476                 add_num2 (&string[length], (tm->tm_year + 1900) / 100,
477                           max - length, pad);
478               break;
479             case 'd':
480               length +=
481                 add_num2 (&string[length], tm->tm_mday, max - length, pad);
482               break;
483             case 'e':
484               length +=
485                 add_num2 (&string[length], tm->tm_mday, max - length, blank);
486               break;
487             case 'D':
488               length +=
489                 strftime (&string[length], max - length, "%m/%d/%y", tm);
490               break;
491             case 'j':
492               length +=
493                 add_num3 (&string[length], tm->tm_yday + 1, max - length, pad);
494               break;
495             case 'm':
496               length +=
497                 add_num2 (&string[length], tm->tm_mon + 1, max - length, pad);
498               break;
499             case 'U':
500               length +=
501                 add_num2 (&string[length], sun_week (tm), max - length, pad);
502               break;
503             case 'w':
504               add_char (tm->tm_wday + '0');
505               break;
506             case 'W':
507               length +=
508                 add_num2 (&string[length], mon_week (tm), max - length, pad);
509               break;
510             case 'x':
511               length +=
512                 strftime (&string[length], max - length, "%m/%d/%y", tm);
513               break;
514             case 'y':
515               length +=
516                 add_num2 (&string[length], tm->tm_year % 100,
517                           max - length, pad);
518               break;
519             case 'Y':
520               add_char ((tm->tm_year + 1900) / 1000 + '0');
521               length +=
522                 add_num3 (&string[length],
523                           (1900 + tm->tm_year) % 1000, max - length, zero);
524               break;
525             }
526         }
527     }
528   add_char (0);
529   return length - 1;
530 }