(sun_week): Make %U work properly.
[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   /* %U Week of the year (Sunday as the first day of the week) as a decimal
246      number [00-53]. All days in a new year preceding the first Sunday are
247      considered to be in week 0.  */
248
249   dl = tm->tm_yday - tm->tm_wday;
250   return dl < 0 ? 0 : dl / 7 + 1;
251 }
252
253 /* Return the week in the year of the time in TM, with the weeks
254    starting on Mondays. */
255
256 static int
257 mon_week (tm)
258      struct tm *tm;
259 {
260   int dl, wday;
261
262   if (tm->tm_wday == 0)
263     wday = 6;
264   else
265     wday = tm->tm_wday - 1;
266   dl = tm->tm_yday - wday;
267   return dl <= 0 ? 0 : dl / 7 + (dl % 7 != 0);
268 }
269
270 #if !defined(HAVE_TM_ZONE) && !defined(HAVE_TZNAME)
271 char *
272 zone_name (tp)
273      struct tm *tp;
274 {
275   char *timezone ();
276   struct timeval tv;
277   struct timezone tz;
278
279   gettimeofday (&tv, &tz);
280   return timezone (tz.tz_minuteswest, tp->tm_isdst);
281 }
282 #endif
283
284 /* Format the time given in TM according to FORMAT, and put the
285    results in STRING.
286    Return the number of characters (not including terminating null)
287    that were put into STRING, or 0 if the length would have
288    exceeded MAX. */
289
290 size_t
291 strftime (string, max, format, tm)
292      char *string;
293      size_t max;
294      const char *format;
295      const struct tm *tm;
296 {
297   enum padding pad;             /* Type of padding to apply. */
298   size_t length = 0;            /* Characters put in STRING so far. */
299
300   for (; *format && length < max; ++format)
301     {
302       if (*format != '%')
303         add_char (*format);
304       else
305         {
306           ++format;
307           /* Modifiers: */
308           if (*format == '-')
309             {
310               pad = none;
311               ++format;
312             }
313           else if (*format == '_')
314             {
315               pad = blank;
316               ++format;
317             }
318           else
319             pad = zero;
320
321           switch (*format)
322             {
323               /* Literal character fields: */
324             case 0:
325             case '%':
326               add_char ('%');
327               break;
328             case 'n':
329               add_char ('\n');
330               break;
331             case 't':
332               add_char ('\t');
333               break;
334             default:
335               add_char (*format);
336               break;
337
338               /* Time fields: */
339             case 'H':
340             case 'k':
341               length +=
342                 add_num2 (&string[length], tm->tm_hour, max - length,
343                           *format == 'H' ? pad : blank);
344               break;
345             case 'I':
346             case 'l':
347               {
348                 int hour12;
349
350                 if (tm->tm_hour == 0)
351                   hour12 = 12;
352                 else if (tm->tm_hour > 12)
353                   hour12 = tm->tm_hour - 12;
354                 else
355                   hour12 = tm->tm_hour;
356                 length +=
357                   add_num2 (&string[length], hour12, max - length,
358                             *format == 'I' ? pad : blank);
359               }
360               break;
361             case 'M':
362               length +=
363                 add_num2 (&string[length], tm->tm_min, max - length, pad);
364               break;
365             case 'p':
366               if (tm->tm_hour < 12)
367                 add_char ('A');
368               else
369                 add_char ('P');
370               add_char ('M');
371               break;
372             case 'r':
373               length +=
374                 strftime (&string[length], max - length, "%I:%M:%S %p", tm);
375               break;
376             case 'R':
377               length +=
378                 strftime (&string[length], max - length, "%H:%M", tm);
379               break;
380
381             case 's':
382               {
383                 struct tm writable_tm;
384                 writable_tm = *tm;
385                 length += add_num_time_t (&string[length], max - length,
386                                           mktime (&writable_tm));
387               }
388               break;
389
390             case 'S':
391               length +=
392                 add_num2 (&string[length], tm->tm_sec, max - length, pad);
393               break;
394             case 'T':
395               length +=
396                 strftime (&string[length], max - length, "%H:%M:%S", tm);
397               break;
398             case 'X':
399               length +=
400                 strftime (&string[length], max - length, "%H:%M:%S", tm);
401               break;
402             case 'z':
403               {
404                 time_t t;
405                 struct tm tml, tmg;
406                 int diff;
407
408                 tml = *tm;
409                 t = mktime (&tml);
410                 tml = *localtime (&t); /* Canonicalize the local time */
411                 tmg = *gmtime (&t);
412
413                 /* Compute the difference */
414
415                 diff = tml.tm_min - tmg.tm_min;
416                 diff += 60 * (tml.tm_hour - tmg.tm_hour);
417
418                 if (tml.tm_mon != tmg.tm_mon)
419                   {
420                     /* We assume no timezone differs from UTC by more than
421                        +- 23 hours.  This should be safe. */
422                     if (tmg.tm_mday == 1)
423                       tml.tm_mday = 0;
424                     else /* tml.tm_mday == 1 */
425                       tmg.tm_mday = 0;
426                   }
427
428                 diff += 1440 * (tml.tm_mday - tmg.tm_mday);
429
430                 length += add_num_tz (&string[length], max - length, diff);
431               }
432               break;
433             case 'Z':
434 #ifdef HAVE_TM_ZONE
435               length += add_str (&string[length], tm->tm_zone, max - length);
436 #else
437 #ifdef HAVE_TZNAME
438               if (tm->tm_isdst && tzname[1] && *tzname[1])
439                 length += add_str (&string[length], tzname[1], max - length);
440               else
441                 length += add_str (&string[length], tzname[0], max - length);
442 #else
443               length += add_str (&string[length], zone_name (tm), max - length);
444 #endif
445 #endif
446               break;
447
448               /* Date fields: */
449             case 'a':
450               add_char (days[tm->tm_wday][0]);
451               add_char (days[tm->tm_wday][1]);
452               add_char (days[tm->tm_wday][2]);
453               break;
454             case 'A':
455               length +=
456                 add_str (&string[length], days[tm->tm_wday], max - length);
457               break;
458             case 'b':
459             case 'h':
460               add_char (months[tm->tm_mon][0]);
461               add_char (months[tm->tm_mon][1]);
462               add_char (months[tm->tm_mon][2]);
463               break;
464             case 'B':
465               length +=
466                 add_str (&string[length], months[tm->tm_mon], max - length);
467               break;
468             case 'c':
469               length +=
470                 strftime (&string[length], max - length,
471                           "%a %b %d %H:%M:%S %Z %Y", tm);
472               break;
473             case 'C':
474               length +=
475                 add_num2 (&string[length], (tm->tm_year + 1900) / 100,
476                           max - length, pad);
477               break;
478             case 'd':
479               length +=
480                 add_num2 (&string[length], tm->tm_mday, max - length, pad);
481               break;
482             case 'e':
483               length +=
484                 add_num2 (&string[length], tm->tm_mday, max - length, blank);
485               break;
486             case 'D':
487               length +=
488                 strftime (&string[length], max - length, "%m/%d/%y", tm);
489               break;
490             case 'j':
491               length +=
492                 add_num3 (&string[length], tm->tm_yday + 1, max - length, pad);
493               break;
494             case 'm':
495               length +=
496                 add_num2 (&string[length], tm->tm_mon + 1, max - length, pad);
497               break;
498             case 'U':
499               length +=
500                 add_num2 (&string[length], sun_week (tm), max - length, pad);
501               break;
502             case 'w':
503               add_char (tm->tm_wday + '0');
504               break;
505             case 'W':
506               length +=
507                 add_num2 (&string[length], mon_week (tm), max - length, pad);
508               break;
509             case 'x':
510               length +=
511                 strftime (&string[length], max - length, "%m/%d/%y", tm);
512               break;
513             case 'y':
514               length +=
515                 add_num2 (&string[length], tm->tm_year % 100,
516                           max - length, pad);
517               break;
518             case 'Y':
519               add_char ((tm->tm_year + 1900) / 1000 + '0');
520               length +=
521                 add_num3 (&string[length],
522                           (1900 + tm->tm_year) % 1000, max - length, zero);
523               break;
524             }
525         }
526     }
527   add_char (0);
528   return length - 1;
529 }