merge with 3.8.4c
[coreutils.git] / lib / strftime.c
blob569a3d448e504bca02e6b5db42721eaf747bb82b
1 /* strftime - custom formatting of date and/or time
2 Copyright (C) 1989, 1991, 1992 Free Software Foundation, Inc.
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.
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.
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. */
18 /* Note: this version of strftime lacks locale support,
19 but it is standalone.
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.
29 Supports a superset of the ANSI C field specifiers.
31 Literal character fields:
32 % %
33 n newline
34 t tab
36 Numeric modifiers (a nonstandard extension):
37 - do not pad the field
38 _ pad the field with spaces
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
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...)
74 David MacKenzie <djm@gnu.ai.mit.edu> */
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
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
94 #if defined(HAVE_TZNAME)
95 extern char *tzname[2];
96 #endif
98 /* Types of padding for numbers in date and time. */
99 enum padding
101 none, blank, zero
104 static char const* const days[] =
106 "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"
109 static char const * const months[] =
111 "January", "February", "March", "April", "May", "June",
112 "July", "August", "September", "October", "November", "December"
115 /* Add character C to STRING and increment LENGTH,
116 unless LENGTH would exceed MAX. */
118 #define add_char(c) \
119 do \
121 if (length + 1 <= max) \
122 string[length++] = (c); \
124 while (0)
126 /* Add a 2 digit number to STRING, padding if specified.
127 Return the number of characters added, up to MAX. */
129 static int
130 add_num2 (string, num, max, pad)
131 char *string;
132 int num;
133 int max;
134 enum padding pad;
136 int top = num / 10;
137 int length = 0;
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;
147 /* Add a 3 digit number to STRING, padding if specified.
148 Return the number of characters added, up to MAX. */
150 static int
151 add_num3 (string, num, max, pad)
152 char *string;
153 int num;
154 int max;
155 enum padding pad;
157 int top = num / 100;
158 int mid = (num - top * 100) / 10;
159 int length = 0;
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;
173 /* Like strncpy except return the number of characters copied. */
175 static int
176 add_str (to, from, max)
177 char *to;
178 char *from;
179 int max;
181 int i;
183 for (i = 0; from[i] && i <= max; ++i)
184 to[i] = from[i];
185 return i;
188 /* Return the week in the year of the time in TM, with the weeks
189 starting on Sundays. */
191 static int
192 sun_week (tm)
193 struct tm *tm;
195 int dl;
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);
206 /* Return the week in the year of the time in TM, with the weeks
207 starting on Mondays. */
209 static int
210 mon_week (tm)
211 struct tm *tm;
213 int dl, wday;
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);
223 #if !defined(HAVE_TM_ZONE) && !defined(HAVE_TZNAME)
224 char *
225 zone_name (tp)
226 struct tm *tp;
228 char *timezone ();
229 struct timeval tv;
230 struct timezone tz;
232 gettimeofday (&tv, &tz);
233 return timezone (tz.tz_minuteswest, tp->tm_isdst);
235 #endif
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. */
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;
250 enum padding pad; /* Type of padding to apply. */
251 size_t length = 0; /* Characters put in STRING so far. */
253 for (; *format && length < max; ++format)
255 if (*format != '%')
256 add_char (*format);
257 else
259 ++format;
260 /* Modifiers: */
261 if (*format == '-')
263 pad = none;
264 ++format;
266 else if (*format == '_')
268 pad = blank;
269 ++format;
271 else
272 pad = zero;
274 switch (*format)
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;
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':
301 int hour12;
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);
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;
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;
439 add_char (0);
440 return length - 1;