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 |
|
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 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> |
|
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 |
|
88 |
|
89 #ifndef STDC_HEADERS |
|
90 time_t mktime (); |
|
91 #endif |
|
92 |
|
93 #if defined(HAVE_TZNAME) |
|
94 extern char *tzname[2]; |
|
95 #endif |
|
96 |
|
97 /* Types of padding for numbers in date and time. */ |
|
98 enum padding |
|
99 { |
|
100 none, blank, zero |
|
101 }; |
|
102 |
|
103 static char const* const days[] = |
|
104 { |
|
105 "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" |
|
106 }; |
|
107 |
|
108 static char const * const months[] = |
|
109 { |
|
110 "January", "February", "March", "April", "May", "June", |
|
111 "July", "August", "September", "October", "November", "December" |
|
112 }; |
|
113 |
|
114 /* Add character C to STRING and increment LENGTH, |
|
115 unless LENGTH would exceed MAX. */ |
|
116 |
|
117 #define add_char(c) \ |
|
118 do \ |
|
119 { \ |
|
120 if (length + 1 <= max) \ |
|
121 string[length++] = (c); \ |
|
122 } \ |
|
123 while (0) |
|
124 |
|
125 /* Add a 2 digit number to STRING, padding if specified. |
|
126 Return the number of characters added, up to MAX. */ |
|
127 |
|
128 static int |
|
129 add_num2 (string, num, max, pad) |
|
130 char *string; |
|
131 int num; |
|
132 int max; |
|
133 enum padding pad; |
|
134 { |
|
135 int top = num / 10; |
|
136 int length = 0; |
|
137 |
|
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; |
|
144 } |
|
145 |
|
146 /* Add a 3 digit number to STRING, padding if specified. |
|
147 Return the number of characters added, up to MAX. */ |
|
148 |
|
149 static int |
|
150 add_num3 (string, num, max, pad) |
|
151 char *string; |
|
152 int num; |
|
153 int max; |
|
154 enum padding pad; |
|
155 { |
|
156 int top = num / 100; |
|
157 int mid = (num - top * 100) / 10; |
|
158 int length = 0; |
|
159 |
|
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; |
|
170 } |
|
171 |
|
172 /* Like strncpy except return the number of characters copied. */ |
|
173 |
|
174 static int |
|
175 add_str (to, from, max) |
|
176 char *to; |
|
177 const char *from; |
|
178 int max; |
|
179 { |
|
180 int i; |
|
181 |
|
182 for (i = 0; from[i] && i <= max; ++i) |
|
183 to[i] = from[i]; |
|
184 return i; |
|
185 } |
|
186 |
|
187 static int |
|
188 add_num_time_t (string, max, num) |
|
189 char *string; |
|
190 int max; |
|
191 time_t num; |
|
192 { |
|
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; |
|
198 |
|
199 if (sizeof (num) > 16) |
|
200 abort (); |
|
201 sprintf (buf, "%lu", (unsigned long) num); |
|
202 length = add_str (string, buf, max); |
|
203 return length; |
|
204 } |
|
205 |
|
206 /* Return the week in the year of the time in TM, with the weeks |
|
207 starting on Sundays. */ |
|
208 |
|
209 static int |
|
210 sun_week (tm) |
|
211 struct tm *tm; |
|
212 { |
|
213 int dl; |
|
214 |
|
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); |
|
222 } |
|
223 |
|
224 /* Return the week in the year of the time in TM, with the weeks |
|
225 starting on Mondays. */ |
|
226 |
|
227 static int |
|
228 mon_week (tm) |
|
229 struct tm *tm; |
|
230 { |
|
231 int dl, wday; |
|
232 |
|
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); |
|
239 } |
|
240 |
|
241 #if !defined(HAVE_TM_ZONE) && !defined(HAVE_TZNAME) |
|
242 char * |
|
243 zone_name (tp) |
|
244 struct tm *tp; |
|
245 { |
|
246 char *timezone (); |
|
247 struct timeval tv; |
|
248 struct timezone tz; |
|
249 |
|
250 gettimeofday (&tv, &tz); |
|
251 return timezone (tz.tz_minuteswest, tp->tm_isdst); |
|
252 } |
|
253 #endif |
|
254 |
|
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. */ |
|
260 |
|
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; |
|
267 { |
|
268 enum padding pad; /* Type of padding to apply. */ |
|
269 size_t length = 0; /* Characters put in STRING so far. */ |
|
270 |
|
271 for (; *format && length < max; ++format) |
|
272 { |
|
273 if (*format != '%') |
|
274 add_char (*format); |
|
275 else |
|
276 { |
|
277 ++format; |
|
278 /* Modifiers: */ |
|
279 if (*format == '-') |
|
280 { |
|
281 pad = none; |
|
282 ++format; |
|
283 } |
|
284 else if (*format == '_') |
|
285 { |
|
286 pad = blank; |
|
287 ++format; |
|
288 } |
|
289 else |
|
290 pad = zero; |
|
291 |
|
292 switch (*format) |
|
293 { |
|
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; |
|
308 |
|
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': |
|
318 { |
|
319 int hour12; |
|
320 |
|
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); |
|
330 } |
|
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; |
|
351 |
|
352 case 's': |
|
353 { |
|
354 struct tm writable_tm; |
|
355 writable_tm = *tm; |
|
356 length += add_num_time_t (&string[length], max - length, |
|
357 mktime (&writable_tm)); |
|
358 } |
|
359 break; |
|
360 |
|
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; |
|
387 |
|
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; |
|
464 } |
|
465 } |
|
466 } |
|
467 add_char (0); |
|
468 return length - 1; |
|
469 } |