Initial revision
[findutils.git] / lib / strftime.c
blob484852a72d13d83361cefdcc8448efe5d774dccc
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 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
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...)
75 David MacKenzie <djm@gnu.ai.mit.edu> */
77 #ifdef HAVE_CONFIG_H
78 #include <config.h>
79 #endif
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
89 #ifndef STDC_HEADERS
90 time_t mktime ();
91 #endif
93 #if defined(HAVE_TZNAME)
94 extern char *tzname[2];
95 #endif
97 /* Types of padding for numbers in date and time. */
98 enum padding
100 none, blank, zero
103 static char const* const days[] =
105 "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"
108 static char const * const months[] =
110 "January", "February", "March", "April", "May", "June",
111 "July", "August", "September", "October", "November", "December"
114 /* Add character C to STRING and increment LENGTH,
115 unless LENGTH would exceed MAX. */
117 #define add_char(c) \
118 do \
120 if (length + 1 <= max) \
121 string[length++] = (c); \
123 while (0)
125 /* Add a 2 digit number to STRING, padding if specified.
126 Return the number of characters added, up to MAX. */
128 static int
129 add_num2 (string, num, max, pad)
130 char *string;
131 int num;
132 int max;
133 enum padding pad;
135 int top = num / 10;
136 int length = 0;
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;
146 /* Add a 3 digit number to STRING, padding if specified.
147 Return the number of characters added, up to MAX. */
149 static int
150 add_num3 (string, num, max, pad)
151 char *string;
152 int num;
153 int max;
154 enum padding pad;
156 int top = num / 100;
157 int mid = (num - top * 100) / 10;
158 int length = 0;
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;
172 /* Like strncpy except return the number of characters copied. */
174 static int
175 add_str (to, from, max)
176 char *to;
177 const char *from;
178 int max;
180 int i;
182 for (i = 0; from[i] && i <= max; ++i)
183 to[i] = from[i];
184 return i;
187 static int
188 add_num_time_t (string, max, num)
189 char *string;
190 int max;
191 time_t num;
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;
199 if (sizeof (num) > 16)
200 abort ();
201 sprintf (buf, "%lu", (unsigned long) num);
202 length = add_str (string, buf, max);
203 return length;
206 /* Return the week in the year of the time in TM, with the weeks
207 starting on Sundays. */
209 static int
210 sun_week (tm)
211 struct tm *tm;
213 int dl;
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);
224 /* Return the week in the year of the time in TM, with the weeks
225 starting on Mondays. */
227 static int
228 mon_week (tm)
229 struct tm *tm;
231 int dl, wday;
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);
241 #if !defined(HAVE_TM_ZONE) && !defined(HAVE_TZNAME)
242 char *
243 zone_name (tp)
244 struct tm *tp;
246 char *timezone ();
247 struct timeval tv;
248 struct timezone tz;
250 gettimeofday (&tv, &tz);
251 return timezone (tz.tz_minuteswest, tp->tm_isdst);
253 #endif
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. */
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;
268 enum padding pad; /* Type of padding to apply. */
269 size_t length = 0; /* Characters put in STRING so far. */
271 for (; *format && length < max; ++format)
273 if (*format != '%')
274 add_char (*format);
275 else
277 ++format;
278 /* Modifiers: */
279 if (*format == '-')
281 pad = none;
282 ++format;
284 else if (*format == '_')
286 pad = blank;
287 ++format;
289 else
290 pad = zero;
292 switch (*format)
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;
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':
319 int hour12;
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);
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;
352 case 's':
354 struct tm writable_tm;
355 writable_tm = *tm;
356 length += add_num_time_t (&string[length], max - length,
357 mktime (&writable_tm));
359 break;
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;
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;
467 add_char (0);
468 return length - 1;