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