Mercurial > hg > xemacs-beta
comparison src/strftime.c @ 0:376386a54a3c r19-14
Import from CVS: tag r19-14
author | cvs |
---|---|
date | Mon, 13 Aug 2007 08:45:50 +0200 |
parents | |
children | 4be1180a9e89 |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:376386a54a3c |
---|---|
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; see the file COPYING. If not, write to | |
16 the Free Software Foundation, Inc., 59 Temple Place - Suite 330, | |
17 Boston, MA 02111-1307, USA. */ | |
18 | |
19 /* Synched up with: FSF 19.30. */ | |
20 | |
21 /* Note: this version of strftime lacks locale support, | |
22 but it is standalone. | |
23 | |
24 Performs `%' substitutions similar to those in printf. Except | |
25 where noted, substituted fields have a fixed size; numeric fields are | |
26 padded if necessary. Padding is with zeros by default; for fields | |
27 that display a single number, padding can be changed or inhibited by | |
28 following the `%' with one of the modifiers described below. Unknown | |
29 field specifiers are copied as normal characters. All other | |
30 characters are copied to the output without change. | |
31 | |
32 Supports a superset of the ANSI C field specifiers. | |
33 | |
34 Literal character fields: | |
35 % % | |
36 n newline | |
37 t tab | |
38 | |
39 Numeric modifiers (a nonstandard extension): | |
40 - do not pad the field | |
41 _ pad the field with spaces | |
42 | |
43 Time fields: | |
44 %H hour (00..23) | |
45 %I hour (01..12) | |
46 %k hour ( 0..23) | |
47 %l hour ( 1..12) | |
48 %M minute (00..59) | |
49 %p locale's AM or PM | |
50 %r time, 12-hour (hh:mm:ss [AP]M) | |
51 %R time, 24-hour (hh:mm) | |
52 %s time in seconds since 00:00:00, Jan 1, 1970 (a nonstandard extension) | |
53 %S second (00..61) | |
54 %T time, 24-hour (hh:mm:ss) | |
55 %X locale's time representation (%H:%M:%S) | |
56 %Z time zone (EDT), or nothing if no time zone is determinable | |
57 | |
58 Date fields: | |
59 %a locale's abbreviated weekday name (Sun..Sat) | |
60 %A locale's full weekday name, variable length (Sunday..Saturday) | |
61 %b locale's abbreviated month name (Jan..Dec) | |
62 %B locale's full month name, variable length (January..December) | |
63 %c locale's date and time (Sat Nov 04 12:02:33 EST 1989) | |
64 %C century (00..99) | |
65 %d day of month (01..31) | |
66 %e day of month ( 1..31) | |
67 %D date (mm/dd/yy) | |
68 %h same as %b | |
69 %j day of year (001..366) | |
70 %m month (01..12) | |
71 %U week number of year with Sunday as first day of week (00..53) | |
72 %w day of week (0..6) | |
73 %W week number of year with Monday as first day of week (00..53) | |
74 %x locale's date representation (mm/dd/yy) | |
75 %y last two digits of year (00..99) | |
76 %Y year (1970...) | |
77 | |
78 David MacKenzie <djm@gnu.ai.mit.edu> */ | |
79 | |
80 #ifdef HAVE_CONFIG_H | |
81 #include <config.h> | |
82 #include "lisp.h" | |
83 #endif | |
84 | |
85 #include <stdio.h> | |
86 #include <sys/types.h> | |
87 #if defined(TM_IN_SYS_TIME) || (!defined(HAVE_TM_ZONE) && !defined(HAVE_TZNAME)) | |
88 #include <sys/time.h> | |
89 #else | |
90 #include <time.h> | |
91 #endif | |
92 | |
93 #ifndef STDC_HEADERS | |
94 time_t mktime (); | |
95 #endif | |
96 | |
97 #if defined(HAVE_TZNAME) | |
98 extern char *tzname[2]; | |
99 #endif | |
100 | |
101 #ifdef emacs | |
102 #define strftime emacs_strftime | |
103 #endif | |
104 | |
105 /* Types of padding for numbers in date and time. */ | |
106 enum padding | |
107 { | |
108 none, blank, zero | |
109 }; | |
110 | |
111 static char CONST* CONST days[] = | |
112 { | |
113 "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" | |
114 }; | |
115 | |
116 static char CONST * CONST months[] = | |
117 { | |
118 "January", "February", "March", "April", "May", "June", | |
119 "July", "August", "September", "October", "November", "December" | |
120 }; | |
121 | |
122 /* Add character C to STRING and increment LENGTH, | |
123 unless LENGTH would exceed MAX. */ | |
124 | |
125 #define add_char(c) do \ | |
126 { \ | |
127 if (length + 1 <= max) \ | |
128 string[length++] = (c); \ | |
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 (char *string, int num, int max, enum padding pad) | |
136 { | |
137 int top = num / 10; | |
138 int length = 0; | |
139 | |
140 if (top == 0 && pad == blank) | |
141 add_char (' '); | |
142 else if (top != 0 || pad == zero) | |
143 add_char (top + '0'); | |
144 add_char (num % 10 + '0'); | |
145 return length; | |
146 } | |
147 | |
148 /* Add a 3 digit number to STRING, padding if specified. | |
149 Return the number of characters added, up to MAX. */ | |
150 | |
151 static int | |
152 add_num3 (char *string, int num, int max, enum padding pad) | |
153 { | |
154 int top = num / 100; | |
155 int mid = (num - top * 100) / 10; | |
156 int length = 0; | |
157 | |
158 if (top == 0 && pad == blank) | |
159 add_char (' '); | |
160 else if (top != 0 || pad == zero) | |
161 add_char (top + '0'); | |
162 if (mid == 0 && top == 0 && pad == blank) | |
163 add_char (' '); | |
164 else if (mid != 0 || top != 0 || pad == zero) | |
165 add_char (mid + '0'); | |
166 add_char (num % 10 + '0'); | |
167 return length; | |
168 } | |
169 | |
170 /* Like strncpy except return the number of characters copied. */ | |
171 | |
172 static int | |
173 add_str (char *to, CONST char *from, int max) | |
174 { | |
175 int i; | |
176 | |
177 for (i = 0; from[i] && i <= max; ++i) | |
178 to[i] = from[i]; | |
179 return i; | |
180 } | |
181 | |
182 static int | |
183 add_num_time_t (char *string, int max, time_t num) | |
184 { | |
185 /* This buffer is large enough to hold the character representation | |
186 (including the trailing NUL) of any unsigned decimal quantity | |
187 whose binary representation fits in 128 bits. */ | |
188 char buf[40]; | |
189 int length; | |
190 | |
191 if (sizeof (num) > 16) | |
192 abort (); | |
193 sprintf (buf, "%lu", (unsigned long) num); | |
194 length = add_str (string, buf, max); | |
195 return length; | |
196 } | |
197 | |
198 /* Return the week in the year of the time in TM, with the weeks | |
199 starting on Sundays. */ | |
200 | |
201 static int | |
202 sun_week (CONST struct tm *tm) | |
203 { | |
204 int dl; | |
205 | |
206 /* Set `dl' to the day in the year of the last day of the week previous | |
207 to the one containing the day specified in TM. If the day specified | |
208 in TM is in the first week of the year, `dl' will be negative or 0. | |
209 Otherwise, calculate the number of complete weeks before our week | |
210 (dl / 7) and add any partial week at the start of the year (dl % 7). */ | |
211 dl = tm->tm_yday - tm->tm_wday; | |
212 return dl <= 0 ? 0 : dl / 7 + (dl % 7 != 0); | |
213 } | |
214 | |
215 /* Return the week in the year of the time in TM, with the weeks | |
216 starting on Mondays. */ | |
217 | |
218 static int | |
219 mon_week (CONST struct tm *tm) | |
220 { | |
221 int dl, wday; | |
222 | |
223 if (tm->tm_wday == 0) | |
224 wday = 6; | |
225 else | |
226 wday = tm->tm_wday - 1; | |
227 dl = tm->tm_yday - wday; | |
228 return dl <= 0 ? 0 : dl / 7 + (dl % 7 != 0); | |
229 } | |
230 | |
231 #if !defined(HAVE_TM_ZONE) && !defined(HAVE_TZNAME) | |
232 char * | |
233 zone_name (struct tm *tp) | |
234 { | |
235 char *timezone (); | |
236 struct timeval tv; | |
237 struct timezone tz; | |
238 | |
239 gettimeofday (&tv, &tz); | |
240 return timezone (tz.tz_minuteswest, tp->tm_isdst); | |
241 } | |
242 #endif | |
243 | |
244 /* Format the time given in TM according to FORMAT, and put the | |
245 results in STRING. | |
246 Return the number of characters (not including terminating null) | |
247 that were put into STRING, or 0 if the length would have | |
248 exceeded MAX. */ | |
249 | |
250 size_t strftime (char *string, size_t max, CONST char *format, | |
251 CONST struct tm *tm); | |
252 | |
253 size_t | |
254 strftime (char *string, size_t max, CONST char *format, CONST struct tm *tm) | |
255 { | |
256 enum padding pad; /* Type of padding to apply. */ | |
257 size_t length = 0; /* Characters put in STRING so far. */ | |
258 | |
259 for (; *format && length < max; ++format) | |
260 { | |
261 if (*format != '%') | |
262 add_char (*format); | |
263 else | |
264 { | |
265 ++format; | |
266 /* Modifiers: */ | |
267 if (*format == '-') | |
268 { | |
269 pad = none; | |
270 ++format; | |
271 } | |
272 else if (*format == '_') | |
273 { | |
274 pad = blank; | |
275 ++format; | |
276 } | |
277 else | |
278 pad = zero; | |
279 | |
280 switch (*format) | |
281 { | |
282 /* Literal character fields: */ | |
283 case 0: | |
284 case '%': | |
285 add_char ('%'); | |
286 break; | |
287 case 'n': | |
288 add_char ('\n'); | |
289 break; | |
290 case 't': | |
291 add_char ('\t'); | |
292 break; | |
293 default: | |
294 add_char (*format); | |
295 break; | |
296 | |
297 /* Time fields: */ | |
298 case 'H': | |
299 case 'k': | |
300 length += | |
301 add_num2 (&string[length], tm->tm_hour, max - length, | |
302 *format == 'H' ? pad : blank); | |
303 break; | |
304 case 'I': | |
305 case 'l': | |
306 { | |
307 int hour12; | |
308 | |
309 if (tm->tm_hour == 0) | |
310 hour12 = 12; | |
311 else if (tm->tm_hour > 12) | |
312 hour12 = tm->tm_hour - 12; | |
313 else | |
314 hour12 = tm->tm_hour; | |
315 length += | |
316 add_num2 (&string[length], hour12, max - length, | |
317 *format == 'I' ? pad : blank); | |
318 } | |
319 break; | |
320 case 'M': | |
321 length += | |
322 add_num2 (&string[length], tm->tm_min, max - length, pad); | |
323 break; | |
324 case 'p': | |
325 if (tm->tm_hour < 12) | |
326 add_char ('A'); | |
327 else | |
328 add_char ('P'); | |
329 add_char ('M'); | |
330 break; | |
331 case 'r': | |
332 length += | |
333 strftime (&string[length], max - length, "%I:%M:%S %p", tm); | |
334 break; | |
335 case 'R': | |
336 length += | |
337 strftime (&string[length], max - length, "%H:%M", tm); | |
338 break; | |
339 | |
340 case 's': | |
341 { | |
342 struct tm writable_tm; | |
343 writable_tm = *tm; | |
344 length += add_num_time_t (&string[length], max - length, | |
345 mktime (&writable_tm)); | |
346 } | |
347 break; | |
348 | |
349 case 'S': | |
350 length += | |
351 add_num2 (&string[length], tm->tm_sec, max - length, pad); | |
352 break; | |
353 case 'T': | |
354 length += | |
355 strftime (&string[length], max - length, "%H:%M:%S", tm); | |
356 break; | |
357 case 'X': | |
358 length += | |
359 strftime (&string[length], max - length, "%H:%M:%S", tm); | |
360 break; | |
361 case 'Z': | |
362 #ifdef HAVE_TM_ZONE | |
363 length += add_str (&string[length], tm->tm_zone, max - length); | |
364 #else | |
365 #ifdef HAVE_TZNAME | |
366 if (tm->tm_isdst && tzname[1] && *tzname[1]) | |
367 length += add_str (&string[length], tzname[1], max - length); | |
368 else | |
369 length += add_str (&string[length], tzname[0], max - length); | |
370 #else | |
371 length += add_str (&string[length], zone_name (tm), max - length); | |
372 #endif | |
373 #endif | |
374 break; | |
375 | |
376 /* Date fields: */ | |
377 case 'a': | |
378 add_char (days[tm->tm_wday][0]); | |
379 add_char (days[tm->tm_wday][1]); | |
380 add_char (days[tm->tm_wday][2]); | |
381 break; | |
382 case 'A': | |
383 length += | |
384 add_str (&string[length], days[tm->tm_wday], max - length); | |
385 break; | |
386 case 'b': | |
387 case 'h': | |
388 add_char (months[tm->tm_mon][0]); | |
389 add_char (months[tm->tm_mon][1]); | |
390 add_char (months[tm->tm_mon][2]); | |
391 break; | |
392 case 'B': | |
393 length += | |
394 add_str (&string[length], months[tm->tm_mon], max - length); | |
395 break; | |
396 case 'c': | |
397 length += | |
398 strftime (&string[length], max - length, | |
399 "%a %b %d %H:%M:%S %Z %Y", tm); | |
400 break; | |
401 case 'C': | |
402 length += | |
403 add_num2 (&string[length], (tm->tm_year + 1900) / 100, | |
404 max - length, pad); | |
405 break; | |
406 case 'd': | |
407 length += | |
408 add_num2 (&string[length], tm->tm_mday, max - length, pad); | |
409 break; | |
410 case 'e': | |
411 length += | |
412 add_num2 (&string[length], tm->tm_mday, max - length, blank); | |
413 break; | |
414 case 'D': | |
415 length += | |
416 strftime (&string[length], max - length, "%m/%d/%y", tm); | |
417 break; | |
418 case 'j': | |
419 length += | |
420 add_num3 (&string[length], tm->tm_yday + 1, max - length, pad); | |
421 break; | |
422 case 'm': | |
423 length += | |
424 add_num2 (&string[length], tm->tm_mon + 1, max - length, pad); | |
425 break; | |
426 case 'U': | |
427 length += | |
428 add_num2 (&string[length], sun_week (tm), max - length, pad); | |
429 break; | |
430 case 'w': | |
431 add_char (tm->tm_wday + '0'); | |
432 break; | |
433 case 'W': | |
434 length += | |
435 add_num2 (&string[length], mon_week (tm), max - length, pad); | |
436 break; | |
437 case 'x': | |
438 length += | |
439 strftime (&string[length], max - length, "%m/%d/%y", tm); | |
440 break; | |
441 case 'y': | |
442 length += | |
443 add_num2 (&string[length], tm->tm_year % 100, | |
444 max - length, pad); | |
445 break; | |
446 case 'Y': | |
447 add_char ((tm->tm_year + 1900) / 1000 + '0'); | |
448 length += | |
449 add_num3 (&string[length], | |
450 (1900 + tm->tm_year) % 1000, max - length, zero); | |
451 break; | |
452 } | |
453 } | |
454 } | |
455 add_char (0); | |
456 return length - 1; | |
457 } |