Rev 4874 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
4349 | Serge | 1 | /* NOTE: This file defines both strftime() and wcsftime(). Take care when |
2 | * making changes. See also wcsftime.c, and note the (small) overlap in the |
||
3 | * manual description, taking care to edit both as needed. */ |
||
4 | /* |
||
5 | * strftime.c |
||
6 | * Original Author: G. Haley |
||
7 | * Additions from: Eric Blake |
||
8 | * Changes to allow dual use as wcstime, also: Craig Howland |
||
9 | * |
||
10 | * Places characters into the array pointed to by s as controlled by the string |
||
11 | * pointed to by format. If the total number of resulting characters including |
||
12 | * the terminating null character is not more than maxsize, returns the number |
||
13 | * of characters placed into the array pointed to by s (not including the |
||
14 | * terminating null character); otherwise zero is returned and the contents of |
||
15 | * the array indeterminate. |
||
16 | */ |
||
17 | |||
18 | /* |
||
19 | FUNCTION |
||
20 | < |
||
21 | |||
22 | INDEX |
||
23 | strftime |
||
24 | |||
25 | ANSI_SYNOPSIS |
||
26 | #include |
||
4921 | Serge | 27 | size_t strftime(char *restrict <[s]>, size_t <[maxsize]>, |
28 | const char *restrict <[format]>, |
||
29 | const struct tm *restrict <[timp]>); |
||
4349 | Serge | 30 | |
31 | TRAD_SYNOPSIS |
||
32 | #include |
||
33 | size_t strftime(<[s]>, <[maxsize]>, <[format]>, <[timp]>) |
||
34 | char *<[s]>; |
||
35 | size_t <[maxsize]>; |
||
36 | char *<[format]>; |
||
37 | struct tm *<[timp]>; |
||
38 | |||
39 | DESCRIPTION |
||
40 | < |
||
41 | <[timp]>) into a null-terminated string, starting at <[s]> and occupying |
||
42 | no more than <[maxsize]> characters. |
||
43 | |||
44 | You control the format of the output using the string at <[format]>. |
||
45 | <<*<[format]>>> can contain two kinds of specifications: text to be |
||
46 | copied literally into the formatted string, and time conversion |
||
47 | specifications. Time conversion specifications are two- and |
||
48 | three-character sequences beginning with `<<%>>' (use `<<%%>>' to |
||
49 | include a percent sign in the output). Each defined conversion |
||
50 | specification selects only the specified field(s) of calendar time |
||
51 | data from <<*<[timp]>>>, and converts it to a string in one of the |
||
52 | following ways: |
||
53 | |||
54 | o+ |
||
55 | o %a |
||
56 | The abbreviated weekday name according to the current locale. [tm_wday] |
||
57 | |||
58 | o %A |
||
59 | The full weekday name according to the current locale. |
||
60 | In the default "C" locale, one of `< |
||
61 | `< |
||
62 | |||
63 | o %b |
||
64 | The abbreviated month name according to the current locale. [tm_mon] |
||
65 | |||
66 | o %B |
||
67 | The full month name according to the current locale. |
||
68 | In the default "C" locale, one of `< |
||
69 | `< |
||
70 | `< |
||
71 | `< |
||
72 | |||
73 | o %c |
||
74 | The preferred date and time representation for the current locale. |
||
75 | [tm_sec, tm_min, tm_hour, tm_mday, tm_mon, tm_year, tm_wday] |
||
76 | |||
77 | o %C |
||
78 | The century, that is, the year divided by 100 then truncated. For |
||
79 | 4-digit years, the result is zero-padded and exactly two characters; |
||
80 | but for other years, there may a negative sign or more digits. In |
||
81 | this way, `<<%C%y>>' is equivalent to `<<%Y>>'. [tm_year] |
||
82 | |||
83 | o %d |
||
84 | The day of the month, formatted with two digits (from `<<01>>' to |
||
85 | `<<31>>'). [tm_mday] |
||
86 | |||
87 | o %D |
||
88 | A string representing the date, in the form `<<"%m/%d/%y">>'. |
||
89 | [tm_mday, tm_mon, tm_year] |
||
90 | |||
91 | o %e |
||
92 | The day of the month, formatted with leading space if single digit |
||
93 | (from `<<1>>' to `<<31>>'). [tm_mday] |
||
94 | |||
95 | o %E< |
||
96 | In some locales, the E modifier selects alternative representations of |
||
97 | certain modifiers < |
||
98 | |||
99 | o %F |
||
100 | A string representing the ISO 8601:2000 date format, in the form |
||
101 | `<<"%Y-%m-%d">>'. [tm_mday, tm_mon, tm_year] |
||
102 | |||
103 | o %g |
||
104 | The last two digits of the week-based year, see specifier %G (from |
||
105 | `<<00>>' to `<<99>>'). [tm_year, tm_wday, tm_yday] |
||
106 | |||
107 | o %G |
||
108 | The week-based year. In the ISO 8601:2000 calendar, week 1 of the year |
||
109 | includes January 4th, and begin on Mondays. Therefore, if January 1st, |
||
110 | 2nd, or 3rd falls on a Sunday, that day and earlier belong to the last |
||
111 | week of the previous year; and if December 29th, 30th, or 31st falls |
||
112 | on Monday, that day and later belong to week 1 of the next year. For |
||
113 | consistency with %Y, it always has at least four characters. |
||
114 | Example: "%G" for Saturday 2nd January 1999 gives "1998", and for |
||
115 | Tuesday 30th December 1997 gives "1998". [tm_year, tm_wday, tm_yday] |
||
116 | |||
117 | o %h |
||
118 | Synonym for "%b". [tm_mon] |
||
119 | |||
120 | o %H |
||
121 | The hour (on a 24-hour clock), formatted with two digits (from |
||
122 | `<<00>>' to `<<23>>'). [tm_hour] |
||
123 | |||
124 | o %I |
||
125 | The hour (on a 12-hour clock), formatted with two digits (from |
||
126 | `<<01>>' to `<<12>>'). [tm_hour] |
||
127 | |||
128 | o %j |
||
129 | The count of days in the year, formatted with three digits |
||
130 | (from `<<001>>' to `<<366>>'). [tm_yday] |
||
131 | |||
132 | o %k |
||
133 | The hour (on a 24-hour clock), formatted with leading space if single |
||
134 | digit (from `<<0>>' to `<<23>>'). Non-POSIX extension (c.p. %I). [tm_hour] |
||
135 | |||
136 | o %l |
||
137 | The hour (on a 12-hour clock), formatted with leading space if single |
||
138 | digit (from `<<1>>' to `<<12>>'). Non-POSIX extension (c.p. %H). [tm_hour] |
||
139 | |||
140 | o %m |
||
141 | The month number, formatted with two digits (from `<<01>>' to `<<12>>'). |
||
142 | [tm_mon] |
||
143 | |||
144 | o %M |
||
145 | The minute, formatted with two digits (from `<<00>>' to `<<59>>'). [tm_min] |
||
146 | |||
147 | o %n |
||
148 | A newline character (`<<\n>>'). |
||
149 | |||
150 | o %O< |
||
151 | In some locales, the O modifier selects alternative digit characters |
||
152 | for certain modifiers < |
||
153 | |||
154 | o %p |
||
155 | Either `< |
||
156 | the current locale. [tm_hour] |
||
157 | |||
158 | o %P |
||
159 | Same as '<<%p>>', but in lowercase. This is a GNU extension. [tm_hour] |
||
160 | |||
161 | o %r |
||
162 | Replaced by the time in a.m. and p.m. notation. In the "C" locale this |
||
163 | is equivalent to "%I:%M:%S %p". In locales which don't define a.m./p.m. |
||
164 | notations, the result is an empty string. [tm_sec, tm_min, tm_hour] |
||
165 | |||
166 | o %R |
||
167 | The 24-hour time, to the minute. Equivalent to "%H:%M". [tm_min, tm_hour] |
||
168 | |||
169 | o %S |
||
170 | The second, formatted with two digits (from `<<00>>' to `<<60>>'). The |
||
171 | value 60 accounts for the occasional leap second. [tm_sec] |
||
172 | |||
173 | o %t |
||
174 | A tab character (`<<\t>>'). |
||
175 | |||
176 | o %T |
||
177 | The 24-hour time, to the second. Equivalent to "%H:%M:%S". [tm_sec, |
||
178 | tm_min, tm_hour] |
||
179 | |||
180 | o %u |
||
181 | The weekday as a number, 1-based from Monday (from `<<1>>' to |
||
182 | `<<7>>'). [tm_wday] |
||
183 | |||
184 | o %U |
||
185 | The week number, where weeks start on Sunday, week 1 contains the first |
||
186 | Sunday in a year, and earlier days are in week 0. Formatted with two |
||
187 | digits (from `<<00>>' to `<<53>>'). See also <<%W>>. [tm_wday, tm_yday] |
||
188 | |||
189 | o %V |
||
190 | The week number, where weeks start on Monday, week 1 contains January 4th, |
||
191 | and earlier days are in the previous year. Formatted with two digits |
||
192 | (from `<<01>>' to `<<53>>'). See also <<%G>>. [tm_year, tm_wday, tm_yday] |
||
193 | |||
194 | o %w |
||
195 | The weekday as a number, 0-based from Sunday (from `<<0>>' to `<<6>>'). |
||
196 | [tm_wday] |
||
197 | |||
198 | o %W |
||
199 | The week number, where weeks start on Monday, week 1 contains the first |
||
200 | Monday in a year, and earlier days are in week 0. Formatted with two |
||
201 | digits (from `<<00>>' to `<<53>>'). [tm_wday, tm_yday] |
||
202 | |||
203 | o %x |
||
204 | Replaced by the preferred date representation in the current locale. |
||
205 | In the "C" locale this is equivalent to "%m/%d/%y". |
||
206 | [tm_mon, tm_mday, tm_year] |
||
207 | |||
208 | o %X |
||
209 | Replaced by the preferred time representation in the current locale. |
||
210 | In the "C" locale this is equivalent to "%H:%M:%S". [tm_sec, tm_min, tm_hour] |
||
211 | |||
212 | o %y |
||
213 | The last two digits of the year (from `<<00>>' to `<<99>>'). [tm_year] |
||
214 | (Implementation interpretation: always positive, even for negative years.) |
||
215 | |||
216 | o %Y |
||
217 | The full year, equivalent to <<%C%y>>. It will always have at least four |
||
218 | characters, but may have more. The year is accurate even when tm_year |
||
219 | added to the offset of 1900 overflows an int. [tm_year] |
||
220 | |||
221 | o %z |
||
222 | The offset from UTC. The format consists of a sign (negative is west of |
||
223 | Greewich), two characters for hour, then two characters for minutes |
||
224 | (-hhmm or +hhmm). If tm_isdst is negative, the offset is unknown and no |
||
225 | output is generated; if it is zero, the offset is the standard offset for |
||
226 | the current time zone; and if it is positive, the offset is the daylight |
||
227 | savings offset for the current timezone. The offset is determined from |
||
228 | the TZ environment variable, as if by calling tzset(). [tm_isdst] |
||
229 | |||
230 | o %Z |
||
231 | The time zone name. If tm_isdst is negative, no output is generated. |
||
232 | Otherwise, the time zone name is based on the TZ environment variable, |
||
233 | as if by calling tzset(). [tm_isdst] |
||
234 | |||
235 | o %% |
||
236 | A single character, `<<%>>'. |
||
237 | o- |
||
238 | |||
239 | RETURNS |
||
240 | When the formatted time takes up no more than <[maxsize]> characters, |
||
241 | the result is the length of the formatted string. Otherwise, if the |
||
242 | formatting operation was abandoned due to lack of room, the result is |
||
243 | <<0>>, and the string starting at <[s]> corresponds to just those |
||
244 | parts of <<*<[format]>>> that could be completely filled in within the |
||
245 | <[maxsize]> limit. |
||
246 | |||
247 | PORTABILITY |
||
248 | ANSI C requires < |
||
249 | <<*<[s]>>> when the formatted string would require more than |
||
250 | <[maxsize]> characters. Unrecognized specifiers and fields of |
||
251 | < |
||
252 | formats expand to 0 bytes, it is wise to set <<*<[s]>>> to a nonzero |
||
253 | value beforehand to distinguish between failure and an empty string. |
||
254 | This implementation does not support < |
||
255 | < |
||
256 | |||
257 | < |
||
258 | |||
259 | BUGS |
||
260 | < |
||
261 | the "C" locale settings. |
||
262 | */ |
||
263 | |||
264 | #include |
||
265 | #include |
||
266 | #include |
||
267 | #include |
||
268 | #include |
||
269 | #include |
||
270 | #include |
||
271 | #include |
||
272 | #include |
||
273 | #include |
||
4921 | Serge | 274 | #include "local.h" |
275 | #include "../locale/timelocal.h" |
||
4349 | Serge | 276 | |
277 | /* Defines to make the file dual use for either strftime() or wcsftime(). |
||
278 | * To get wcsftime, define MAKE_WCSFTIME. |
||
279 | * To get strftime, do not define MAKE_WCSFTIME. |
||
280 | * Names are kept friendly to strftime() usage. The biggest ugliness is the |
||
281 | * use of the CQ() macro to make either regular character constants and |
||
282 | * string literals or wide-character constants and wide-character-string |
||
283 | * literals, as appropriate. */ |
||
284 | #if !defined(MAKE_WCSFTIME) |
||
285 | # define CHAR char /* string type basis */ |
||
286 | # define CQ(a) a /* character constant qualifier */ |
||
287 | # define SFLG /* %s flag (null for normal char) */ |
||
288 | # define _ctloc(x) (ctloclen = strlen (ctloc = _CurrentTimeLocale->x), ctloc) |
||
4921 | Serge | 289 | # define snprintf sniprintf /* avoid to pull in FP functions. */ |
4349 | Serge | 290 | # define TOLOWER(c) tolower((int)(unsigned char)(c)) |
291 | # define STRTOUL(c,p,b) strtoul((c),(p),(b)) |
||
292 | # define STRCPY(a,b) strcpy((a),(b)) |
||
293 | # define STRCHR(a,b) strchr((a),(b)) |
||
294 | # define STRLEN(a) strlen(a) |
||
295 | # else |
||
296 | # define strftime wcsftime /* Alternate function name */ |
||
297 | # define CHAR wchar_t /* string type basis */ |
||
298 | # define CQ(a) L##a /* character constant qualifier */ |
||
299 | # define snprintf swprintf /* wide-char equivalent function name */ |
||
300 | # define strncmp wcsncmp /* wide-char equivalent function name */ |
||
301 | # define TOLOWER(c) towlower((wint_t)(c)) |
||
302 | # define STRTOUL(c,p,b) wcstoul((c),(p),(b)) |
||
303 | # define STRCPY(a,b) wcscpy((a),(b)) |
||
304 | # define STRCHR(a,b) wcschr((a),(b)) |
||
305 | # define STRLEN(a) wcslen(a) |
||
306 | # define SFLG "l" /* %s flag (l for wide char) */ |
||
307 | # ifdef __HAVE_LOCALE_INFO_EXTENDED__ |
||
308 | # define _ctloc(x) (ctloclen = wcslen (ctloc = _CurrentTimeLocale->w##x), \ |
||
309 | ctloc) |
||
310 | # else |
||
311 | # define CTLOCBUFLEN 256 /* Arbitrary big buffer size */ |
||
312 | const wchar_t * |
||
313 | __ctloc (wchar_t *buf, const char *elem, size_t *len_ret) |
||
314 | { |
||
315 | buf[CTLOCBUFLEN - 1] = L'\0'; |
||
316 | *len_ret = mbstowcs (buf, elem, CTLOCBUFLEN - 1); |
||
317 | if (*len_ret == (size_t) -1 ) |
||
318 | *len_ret = 0; |
||
319 | return buf; |
||
320 | } |
||
321 | # define _ctloc(x) (ctloc = __ctloc (ctlocbuf, _CurrentTimeLocale->x, \ |
||
322 | &ctloclen)) |
||
323 | # endif |
||
324 | #endif /* MAKE_WCSFTIME */ |
||
325 | |||
4921 | Serge | 326 | #define CHECK_LENGTH() if (len < 0 || (count += len) >= maxsize) \ |
327 | return 0 |
||
4349 | Serge | 328 | |
4921 | Serge | 329 | /* Enforce the coding assumptions that YEAR_BASE is positive. (%C, %Y, etc.) */ |
330 | #if YEAR_BASE < 0 |
||
331 | # error "YEAR_BASE < 0" |
||
332 | #endif |
||
333 | |||
334 | static _CONST int dname_len[7] = |
||
335 | {6, 6, 7, 9, 8, 6, 8}; |
||
336 | |||
337 | /* Using the tm_year, tm_wday, and tm_yday components of TIM_P, return |
||
338 | -1, 0, or 1 as the adjustment to add to the year for the ISO week |
||
339 | numbering used in "%g%G%V", avoiding overflow. */ |
||
340 | static int |
||
341 | _DEFUN (iso_year_adjust, (tim_p), |
||
4349 | Serge | 342 | _CONST struct tm *tim_p) |
343 | { |
||
4921 | Serge | 344 | /* Account for fact that tm_year==0 is year 1900. */ |
345 | int leap = isleap (tim_p->tm_year + (YEAR_BASE |
||
346 | - (tim_p->tm_year < 0 ? 0 : 2000))); |
||
4349 | Serge | 347 | |
4921 | Serge | 348 | /* Pack the yday, wday, and leap year into a single int since there are so |
349 | many disparate cases. */ |
||
350 | #define PACK(yd, wd, lp) (((yd) << 4) + (wd << 1) + (lp)) |
||
351 | switch (PACK (tim_p->tm_yday, tim_p->tm_wday, leap)) |
||
352 | { |
||
353 | case PACK (0, 5, 0): /* Jan 1 is Fri, not leap. */ |
||
354 | case PACK (0, 6, 0): /* Jan 1 is Sat, not leap. */ |
||
355 | case PACK (0, 0, 0): /* Jan 1 is Sun, not leap. */ |
||
356 | case PACK (0, 5, 1): /* Jan 1 is Fri, leap year. */ |
||
357 | case PACK (0, 6, 1): /* Jan 1 is Sat, leap year. */ |
||
358 | case PACK (0, 0, 1): /* Jan 1 is Sun, leap year. */ |
||
359 | case PACK (1, 6, 0): /* Jan 2 is Sat, not leap. */ |
||
360 | case PACK (1, 0, 0): /* Jan 2 is Sun, not leap. */ |
||
361 | case PACK (1, 6, 1): /* Jan 2 is Sat, leap year. */ |
||
362 | case PACK (1, 0, 1): /* Jan 2 is Sun, leap year. */ |
||
363 | case PACK (2, 0, 0): /* Jan 3 is Sun, not leap. */ |
||
364 | case PACK (2, 0, 1): /* Jan 3 is Sun, leap year. */ |
||
365 | return -1; /* Belongs to last week of previous year. */ |
||
366 | case PACK (362, 1, 0): /* Dec 29 is Mon, not leap. */ |
||
367 | case PACK (363, 1, 1): /* Dec 29 is Mon, leap year. */ |
||
368 | case PACK (363, 1, 0): /* Dec 30 is Mon, not leap. */ |
||
369 | case PACK (363, 2, 0): /* Dec 30 is Tue, not leap. */ |
||
370 | case PACK (364, 1, 1): /* Dec 30 is Mon, leap year. */ |
||
371 | case PACK (364, 2, 1): /* Dec 30 is Tue, leap year. */ |
||
372 | case PACK (364, 1, 0): /* Dec 31 is Mon, not leap. */ |
||
373 | case PACK (364, 2, 0): /* Dec 31 is Tue, not leap. */ |
||
374 | case PACK (364, 3, 0): /* Dec 31 is Wed, not leap. */ |
||
375 | case PACK (365, 1, 1): /* Dec 31 is Mon, leap year. */ |
||
376 | case PACK (365, 2, 1): /* Dec 31 is Tue, leap year. */ |
||
377 | case PACK (365, 3, 1): /* Dec 31 is Wed, leap year. */ |
||
378 | return 1; /* Belongs to first week of next year. */ |
||
379 | } |
||
380 | return 0; /* Belongs to specified year. */ |
||
381 | #undef PACK |
||
382 | } |
||
383 | |||
384 | #ifdef _WANT_C99_TIME_FORMATS |
||
385 | typedef struct { |
||
386 | int year; |
||
387 | CHAR *era_C; |
||
388 | CHAR *era_Y; |
||
389 | } era_info_t; |
||
390 | |||
391 | static era_info_t * |
||
392 | #if defined (MAKE_WCSFTIME) && defined (__HAVE_LOCALE_INFO_EXTENDED__) |
||
393 | get_era_info (const struct tm *tim_p, const wchar_t *era) |
||
394 | #else |
||
395 | get_era_info (const struct tm *tim_p, const char *era) |
||
396 | #endif |
||
397 | { |
||
398 | #if defined (MAKE_WCSFTIME) && defined (__HAVE_LOCALE_INFO_EXTENDED__) |
||
399 | wchar_t *c; |
||
400 | const wchar_t *dir; |
||
401 | # define ERA_STRCHR(a,b) wcschr((a),(b)) |
||
402 | # define ERA_STRNCPY(a,b,c) wcsncpy((a),(b),(c)) |
||
403 | # define ERA_STRTOL(a,b,c) wcstol((a),(b),(c)) |
||
404 | #else |
||
405 | char *c; |
||
406 | const char *dir; |
||
407 | # define ERA_STRCHR(a,b) strchr((a),(b)) |
||
408 | # define ERA_STRNCPY(a,b,c) strncpy((a),(b),(c)) |
||
409 | # define ERA_STRTOL(a,b,c) strtol((a),(b),(c)) |
||
410 | #endif |
||
411 | long offset; |
||
412 | struct tm stm, etm; |
||
413 | era_info_t *ei; |
||
414 | |||
415 | ei = (era_info_t *) calloc (1, sizeof (era_info_t)); |
||
416 | if (!ei) |
||
417 | return NULL; |
||
418 | |||
419 | stm.tm_isdst = etm.tm_isdst = 0; |
||
420 | while (era) |
||
421 | { |
||
422 | dir = era; |
||
423 | era += 2; |
||
424 | offset = ERA_STRTOL (era, &c, 10); |
||
425 | era = c + 1; |
||
426 | stm.tm_year = ERA_STRTOL (era, &c, 10) - YEAR_BASE; |
||
427 | /* Adjust offset for negative gregorian dates. */ |
||
428 | if (stm.tm_year <= -YEAR_BASE) |
||
429 | ++stm.tm_year; |
||
430 | stm.tm_mon = ERA_STRTOL (c + 1, &c, 10) - 1; |
||
431 | stm.tm_mday = ERA_STRTOL (c + 1, &c, 10); |
||
432 | stm.tm_hour = stm.tm_min = stm.tm_sec = 0; |
||
433 | era = c + 1; |
||
434 | if (era[0] == '-' && era[1] == '*') |
||
435 | { |
||
436 | etm = stm; |
||
437 | stm.tm_year = INT_MIN; |
||
438 | stm.tm_mon = stm.tm_mday = stm.tm_hour = stm.tm_min = stm.tm_sec = 0; |
||
439 | era += 3; |
||
440 | } |
||
441 | else if (era[0] == '+' && era[1] == '*') |
||
442 | { |
||
443 | etm.tm_year = INT_MAX; |
||
444 | etm.tm_mon = 11; |
||
445 | etm.tm_mday = 31; |
||
446 | etm.tm_hour = 23; |
||
447 | etm.tm_min = etm.tm_sec = 59; |
||
448 | era += 3; |
||
449 | } |
||
450 | else |
||
451 | { |
||
452 | etm.tm_year = ERA_STRTOL (era, &c, 10) - YEAR_BASE; |
||
453 | /* Adjust offset for negative gregorian dates. */ |
||
454 | if (etm.tm_year <= -YEAR_BASE) |
||
455 | ++etm.tm_year; |
||
456 | etm.tm_mon = ERA_STRTOL (c + 1, &c, 10) - 1; |
||
457 | etm.tm_mday = ERA_STRTOL (c + 1, &c, 10); |
||
458 | etm.tm_mday = 31; |
||
459 | etm.tm_hour = 23; |
||
460 | etm.tm_min = etm.tm_sec = 59; |
||
461 | era = c + 1; |
||
462 | } |
||
463 | if ((tim_p->tm_year > stm.tm_year |
||
464 | || (tim_p->tm_year == stm.tm_year |
||
465 | && (tim_p->tm_mon > stm.tm_mon |
||
466 | || (tim_p->tm_mon == stm.tm_mon |
||
467 | && tim_p->tm_mday >= stm.tm_mday)))) |
||
468 | && (tim_p->tm_year < etm.tm_year |
||
469 | || (tim_p->tm_year == etm.tm_year |
||
470 | && (tim_p->tm_mon < etm.tm_mon |
||
471 | || (tim_p->tm_mon == etm.tm_mon |
||
472 | && tim_p->tm_mday <= etm.tm_mday))))) |
||
473 | { |
||
474 | /* Gotcha */ |
||
475 | size_t len; |
||
476 | |||
477 | /* year */ |
||
478 | if (*dir == '+' && stm.tm_year != INT_MIN) |
||
479 | ei->year = tim_p->tm_year - stm.tm_year + offset; |
||
480 | else |
||
481 | ei->year = etm.tm_year - tim_p->tm_year + offset; |
||
482 | /* era_C */ |
||
483 | c = ERA_STRCHR (era, ':'); |
||
484 | #if defined (MAKE_WCSFTIME) && !defined (__HAVE_LOCALE_INFO_EXTENDED__) |
||
485 | len = mbsnrtowcs (NULL, &era, c - era, 0, NULL); |
||
486 | if (len == (size_t) -1) |
||
487 | { |
||
488 | free (ei); |
||
489 | return NULL; |
||
490 | } |
||
491 | #else |
||
492 | len = c - era; |
||
493 | #endif |
||
494 | ei->era_C = (CHAR *) malloc ((len + 1) * sizeof (CHAR)); |
||
495 | if (!ei->era_C) |
||
496 | { |
||
497 | free (ei); |
||
498 | return NULL; |
||
499 | } |
||
500 | #if defined (MAKE_WCSFTIME) && !defined (__HAVE_LOCALE_INFO_EXTENDED__) |
||
501 | len = mbsnrtowcs (ei->era_C, &era, c - era, len + 1, NULL); |
||
502 | #else |
||
503 | ERA_STRNCPY (ei->era_C, era, len); |
||
504 | era += len; |
||
505 | #endif |
||
506 | ei->era_C[len] = CQ('\0'); |
||
507 | /* era_Y */ |
||
508 | ++era; |
||
509 | c = ERA_STRCHR (era, ';'); |
||
510 | if (!c) |
||
511 | c = ERA_STRCHR (era, '\0'); |
||
512 | #if defined (MAKE_WCSFTIME) && !defined (__HAVE_LOCALE_INFO_EXTENDED__) |
||
513 | len = mbsnrtowcs (NULL, &era, c - era, 0, NULL); |
||
514 | if (len == (size_t) -1) |
||
515 | { |
||
516 | free (ei->era_C); |
||
517 | free (ei); |
||
518 | return NULL; |
||
519 | } |
||
520 | #else |
||
521 | len = c - era; |
||
522 | #endif |
||
523 | ei->era_Y = (CHAR *) malloc ((len + 1) * sizeof (CHAR)); |
||
524 | if (!ei->era_Y) |
||
525 | { |
||
526 | free (ei->era_C); |
||
527 | free (ei); |
||
528 | return NULL; |
||
529 | } |
||
530 | #if defined (MAKE_WCSFTIME) && !defined (__HAVE_LOCALE_INFO_EXTENDED__) |
||
531 | len = mbsnrtowcs (ei->era_Y, &era, c - era, len + 1, NULL); |
||
532 | #else |
||
533 | ERA_STRNCPY (ei->era_Y, era, len); |
||
534 | era += len; |
||
535 | #endif |
||
536 | ei->era_Y[len] = CQ('\0'); |
||
537 | return ei; |
||
538 | } |
||
539 | else |
||
540 | era = ERA_STRCHR (era, ';'); |
||
541 | if (era) |
||
542 | ++era; |
||
543 | } |
||
544 | return NULL; |
||
545 | } |
||
546 | |||
547 | static void |
||
548 | free_era_info (era_info_t *ei) |
||
549 | { |
||
550 | free (ei->era_C); |
||
551 | free (ei->era_Y); |
||
552 | free (ei); |
||
553 | } |
||
554 | |||
555 | typedef struct { |
||
556 | size_t num; |
||
557 | CHAR **digit; |
||
558 | CHAR *buffer; |
||
559 | } alt_digits_t; |
||
560 | |||
561 | static alt_digits_t * |
||
562 | #if defined (MAKE_WCSFTIME) && defined (__HAVE_LOCALE_INFO_EXTENDED__) |
||
563 | get_alt_digits (const wchar_t *alt_digits) |
||
564 | #else |
||
565 | get_alt_digits (const char *alt_digits) |
||
566 | #endif |
||
567 | { |
||
568 | alt_digits_t *adi; |
||
569 | #if defined (MAKE_WCSFTIME) && defined (__HAVE_LOCALE_INFO_EXTENDED__) |
||
570 | const wchar_t *a, *e; |
||
571 | # define ALT_STRCHR(a,b) wcschr((a),(b)) |
||
572 | # define ALT_STRCPY(a,b) wcscpy((a),(b)) |
||
573 | # define ALT_STRLEN(a) wcslen(a) |
||
574 | #else |
||
575 | const char *a, *e; |
||
576 | # define ALT_STRCHR(a,b) strchr((a),(b)) |
||
577 | # define ALT_STRCPY(a,b) strcpy((a),(b)) |
||
578 | # define ALT_STRLEN(a) strlen(a) |
||
579 | #endif |
||
580 | CHAR *aa, *ae; |
||
581 | size_t len; |
||
582 | |||
583 | adi = (alt_digits_t *) calloc (1, sizeof (alt_digits_t)); |
||
584 | if (!adi) |
||
585 | return NULL; |
||
586 | |||
587 | /* Compute number of alt_digits. */ |
||
588 | adi->num = 1; |
||
589 | for (a = alt_digits; (e = ALT_STRCHR (a, ';')) != NULL; a = e + 1) |
||
590 | ++adi->num; |
||
591 | /* Allocate the `digit' array, which is an array of `num' pointers into |
||
592 | `buffer'. */ |
||
593 | adi->digit = (CHAR **) calloc (adi->num, sizeof (CHAR **)); |
||
594 | if (!adi->digit) |
||
595 | { |
||
596 | free (adi); |
||
597 | return NULL; |
||
598 | } |
||
599 | /* Compute memory required for `buffer'. */ |
||
600 | #if defined (MAKE_WCSFTIME) && !defined (__HAVE_LOCALE_INFO_EXTENDED__) |
||
601 | len = mbstowcs (NULL, alt_digits, 0); |
||
602 | if (len == (size_t) -1) |
||
603 | { |
||
604 | free (adi->digit); |
||
605 | free (adi); |
||
606 | return NULL; |
||
607 | } |
||
608 | #else |
||
609 | len = ALT_STRLEN (alt_digits); |
||
610 | #endif |
||
611 | /* Allocate it. */ |
||
612 | adi->buffer = (CHAR *) malloc ((len + 1) * sizeof (CHAR)); |
||
613 | if (!adi->buffer) |
||
614 | { |
||
615 | free (adi->digit); |
||
616 | free (adi); |
||
617 | return NULL; |
||
618 | } |
||
619 | /* Store digits in it. */ |
||
620 | #if defined (MAKE_WCSFTIME) && !defined (__HAVE_LOCALE_INFO_EXTENDED__) |
||
621 | mbstowcs (adi->buffer, alt_digits, len + 1); |
||
622 | #else |
||
623 | ALT_STRCPY (adi->buffer, alt_digits); |
||
624 | #endif |
||
625 | /* Store the pointers into `buffer' into the appropriate `digit' slot. */ |
||
626 | for (len = 0, aa = adi->buffer; (ae = STRCHR (aa, CQ(';'))) != NULL; |
||
627 | ++len, aa = ae + 1) |
||
628 | { |
||
629 | *ae = '\0'; |
||
630 | adi->digit[len] = aa; |
||
631 | } |
||
632 | adi->digit[len] = aa; |
||
633 | return adi; |
||
634 | } |
||
635 | |||
636 | static void |
||
637 | free_alt_digits (alt_digits_t *adi) |
||
638 | { |
||
639 | free (adi->digit); |
||
640 | free (adi->buffer); |
||
641 | free (adi); |
||
642 | } |
||
643 | |||
644 | /* Return 0 if no alt_digit is available for a number. |
||
645 | Return -1 if buffer size isn't sufficient to hold alternative digit. |
||
646 | Return length of new digit otherwise. */ |
||
647 | static int |
||
648 | conv_to_alt_digits (CHAR *buf, size_t bufsiz, unsigned num, alt_digits_t *adi) |
||
649 | { |
||
650 | if (num < adi->num) |
||
651 | { |
||
652 | size_t len = STRLEN (adi->digit[num]); |
||
653 | if (bufsiz < len) |
||
654 | return -1; |
||
655 | STRCPY (buf, adi->digit[num]); |
||
656 | return (int) len; |
||
657 | } |
||
4349 | Serge | 658 | return 0; |
659 | } |
||
660 | |||
4921 | Serge | 661 | static size_t __strftime (CHAR *, size_t, const CHAR *, const struct tm *, |
662 | era_info_t **, alt_digits_t **); |
||
4349 | Serge | 663 | |
4921 | Serge | 664 | size_t |
665 | _DEFUN (strftime, (s, maxsize, format, tim_p), |
||
666 | CHAR *__restrict s _AND |
||
667 | size_t maxsize _AND |
||
668 | _CONST CHAR *__restrict format _AND |
||
669 | _CONST struct tm *__restrict tim_p) |
||
670 | { |
||
671 | era_info_t *era_info = NULL; |
||
672 | alt_digits_t *alt_digits = NULL; |
||
673 | size_t ret = __strftime (s, maxsize, format, tim_p, &era_info, &alt_digits); |
||
674 | if (era_info) |
||
675 | free_era_info (era_info); |
||
676 | if (alt_digits) |
||
677 | free_alt_digits (alt_digits); |
||
678 | return ret; |
||
679 | } |
||
680 | |||
681 | static size_t |
||
682 | __strftime (CHAR *s, size_t maxsize, const CHAR *format, |
||
683 | const struct tm *tim_p, era_info_t **era_info, |
||
684 | alt_digits_t **alt_digits) |
||
685 | #else /* !_WANT_C99_TIME_FORMATS */ |
||
686 | # define __strftime(s,m,f,t,e,a) strftime((s),(m),(f),(t)) |
||
687 | |||
688 | size_t |
||
689 | _DEFUN (strftime, (s, maxsize, format, tim_p), |
||
690 | CHAR *__restrict s _AND |
||
691 | size_t maxsize _AND |
||
692 | _CONST CHAR *__restrict format _AND |
||
693 | _CONST struct tm *__restrict tim_p) |
||
694 | #endif /* !_WANT_C99_TIME_FORMATS */ |
||
695 | { |
||
696 | size_t count = 0; |
||
697 | int i, len = 0; |
||
698 | const CHAR *ctloc; |
||
699 | #if defined (MAKE_WCSFTIME) && !defined (__HAVE_LOCALE_INFO_EXTENDED__) |
||
700 | CHAR ctlocbuf[CTLOCBUFLEN]; |
||
701 | #endif |
||
702 | size_t ctloclen; |
||
703 | CHAR alt; |
||
704 | CHAR pad; |
||
705 | unsigned long width; |
||
706 | |||
707 | struct lc_time_T *_CurrentTimeLocale = __get_current_time_locale (); |
||
708 | for (;;) |
||
709 | { |
||
710 | while (*format && *format != CQ('%')) |
||
711 | { |
||
712 | if (count < maxsize - 1) |
||
713 | s[count++] = *format++; |
||
714 | else |
||
715 | return 0; |
||
716 | } |
||
717 | if (*format == CQ('\0')) |
||
718 | break; |
||
719 | format++; |
||
720 | pad = '\0'; |
||
721 | width = 0; |
||
722 | |||
723 | /* POSIX-1.2008 feature: '0' and '+' modifiers require 0-padding with |
||
724 | slightly different semantics. */ |
||
725 | if (*format == CQ('0') || *format == CQ('+')) |
||
726 | pad = *format++; |
||
727 | |||
728 | /* POSIX-1.2008 feature: A minimum field width can be specified. */ |
||
729 | if (*format >= CQ('1') && *format <= CQ('9')) |
||
730 | { |
||
731 | CHAR *fp; |
||
732 | width = STRTOUL (format, &fp, 10); |
||
733 | format = fp; |
||
734 | } |
||
735 | |||
736 | alt = CQ('\0'); |
||
737 | if (*format == CQ('E')) |
||
738 | { |
||
739 | alt = *format++; |
||
740 | #ifdef _WANT_C99_TIME_FORMATS |
||
741 | #if defined (MAKE_WCSFTIME) && defined (__HAVE_LOCALE_INFO_EXTENDED__) |
||
742 | if (!*era_info && *_CurrentTimeLocale->wera) |
||
743 | *era_info = get_era_info (tim_p, _CurrentTimeLocale->wera); |
||
744 | #else |
||
745 | if (!*era_info && *_CurrentTimeLocale->era) |
||
746 | *era_info = get_era_info (tim_p, _CurrentTimeLocale->era); |
||
747 | #endif |
||
748 | #endif /* _WANT_C99_TIME_FORMATS */ |
||
749 | } |
||
750 | else if (*format == CQ('O')) |
||
751 | { |
||
752 | alt = *format++; |
||
753 | #ifdef _WANT_C99_TIME_FORMATS |
||
754 | #if defined (MAKE_WCSFTIME) && defined (__HAVE_LOCALE_INFO_EXTENDED__) |
||
755 | if (!*alt_digits && *_CurrentTimeLocale->walt_digits) |
||
756 | *alt_digits = get_alt_digits (_CurrentTimeLocale->walt_digits); |
||
757 | #else |
||
758 | if (!*alt_digits && *_CurrentTimeLocale->alt_digits) |
||
759 | *alt_digits = get_alt_digits (_CurrentTimeLocale->alt_digits); |
||
760 | #endif |
||
761 | #endif /* _WANT_C99_TIME_FORMATS */ |
||
762 | } |
||
763 | |||
764 | switch (*format) |
||
765 | { |
||
766 | case CQ('a'): |
||
767 | _ctloc (wday[tim_p->tm_wday]); |
||
768 | for (i = 0; i < ctloclen; i++) |
||
769 | { |
||
770 | if (count < maxsize - 1) |
||
771 | s[count++] = ctloc[i]; |
||
772 | else |
||
773 | return 0; |
||
774 | } |
||
775 | break; |
||
776 | case CQ('A'): |
||
777 | _ctloc (weekday[tim_p->tm_wday]); |
||
778 | for (i = 0; i < ctloclen; i++) |
||
779 | { |
||
780 | if (count < maxsize - 1) |
||
781 | s[count++] = ctloc[i]; |
||
782 | else |
||
783 | return 0; |
||
784 | } |
||
785 | break; |
||
786 | case CQ('b'): |
||
787 | case CQ('h'): |
||
788 | _ctloc (mon[tim_p->tm_mon]); |
||
789 | for (i = 0; i < ctloclen; i++) |
||
790 | { |
||
791 | if (count < maxsize - 1) |
||
792 | s[count++] = ctloc[i]; |
||
793 | else |
||
794 | return 0; |
||
795 | } |
||
796 | break; |
||
797 | case CQ('B'): |
||
798 | _ctloc (month[tim_p->tm_mon]); |
||
799 | for (i = 0; i < ctloclen; i++) |
||
800 | { |
||
801 | if (count < maxsize - 1) |
||
802 | s[count++] = ctloc[i]; |
||
803 | else |
||
804 | return 0; |
||
805 | } |
||
806 | break; |
||
807 | case CQ('c'): |
||
808 | #ifdef _WANT_C99_TIME_FORMATS |
||
809 | if (alt == 'E' && *era_info && *_CurrentTimeLocale->era_d_t_fmt) |
||
810 | _ctloc (era_d_t_fmt); |
||
811 | else |
||
812 | #endif /* _WANT_C99_TIME_FORMATS */ |
||
813 | _ctloc (c_fmt); |
||
814 | goto recurse; |
||
815 | case CQ('r'): |
||
816 | _ctloc (ampm_fmt); |
||
817 | goto recurse; |
||
818 | case CQ('x'): |
||
819 | #ifdef _WANT_C99_TIME_FORMATS |
||
820 | if (alt == 'E' && *era_info && *_CurrentTimeLocale->era_d_fmt) |
||
821 | _ctloc (era_d_fmt); |
||
822 | else |
||
823 | #endif /* _WANT_C99_TIME_FORMATS */ |
||
824 | _ctloc (x_fmt); |
||
825 | goto recurse; |
||
826 | case CQ('X'): |
||
827 | #ifdef _WANT_C99_TIME_FORMATS |
||
828 | if (alt == 'E' && *era_info && *_CurrentTimeLocale->era_t_fmt) |
||
829 | _ctloc (era_t_fmt); |
||
830 | else |
||
831 | #endif /* _WANT_C99_TIME_FORMATS */ |
||
832 | _ctloc (X_fmt); |
||
833 | recurse: |
||
834 | if (*ctloc) |
||
835 | { |
||
836 | /* Recurse to avoid need to replicate %Y formation. */ |
||
837 | len = __strftime (&s[count], maxsize - count, ctloc, tim_p, |
||
838 | era_info, alt_digits); |
||
839 | if (len > 0) |
||
840 | count += len; |
||
841 | else |
||
842 | return 0; |
||
843 | } |
||
844 | break; |
||
845 | case CQ('C'): |
||
846 | { |
||
847 | /* Examples of (tm_year + YEAR_BASE) that show how %Y == %C%y |
||
848 | with 32-bit int. |
||
849 | %Y %C %y |
||
850 | 2147485547 21474855 47 |
||
851 | 10000 100 00 |
||
852 | 9999 99 99 |
||
853 | 0999 09 99 |
||
854 | 0099 00 99 |
||
855 | 0001 00 01 |
||
856 | 0000 00 00 |
||
857 | -001 -0 01 |
||
858 | -099 -0 99 |
||
859 | -999 -9 99 |
||
860 | -1000 -10 00 |
||
861 | -10000 -100 00 |
||
862 | -2147481748 -21474817 48 |
||
863 | |||
864 | Be careful of both overflow and sign adjustment due to the |
||
865 | asymmetric range of years. |
||
866 | */ |
||
867 | #ifdef _WANT_C99_TIME_FORMATS |
||
868 | if (alt == 'E' && *era_info) |
||
869 | len = snprintf (&s[count], maxsize - count, CQ("%" SFLG "s"), |
||
870 | (*era_info)->era_C); |
||
871 | else |
||
872 | #endif /* _WANT_C99_TIME_FORMATS */ |
||
873 | { |
||
874 | CHAR *fmt = CQ("%s%.*d"); |
||
875 | char *pos = ""; |
||
876 | int neg = tim_p->tm_year < -YEAR_BASE; |
||
877 | int century = tim_p->tm_year >= 0 |
||
878 | ? tim_p->tm_year / 100 + YEAR_BASE / 100 |
||
879 | : abs (tim_p->tm_year + YEAR_BASE) / 100; |
||
880 | if (pad) /* '0' or '+' */ |
||
881 | { |
||
882 | fmt = CQ("%s%0.*d"); |
||
883 | if (century >= 100 && pad == CQ('+')) |
||
884 | pos = "+"; |
||
885 | } |
||
886 | if (width < 2) |
||
887 | width = 2; |
||
888 | len = snprintf (&s[count], maxsize - count, fmt, |
||
889 | neg ? "-" : pos, width - neg, century); |
||
890 | } |
||
891 | CHECK_LENGTH (); |
||
892 | } |
||
893 | break; |
||
894 | case CQ('d'): |
||
895 | case CQ('e'): |
||
896 | #ifdef _WANT_C99_TIME_FORMATS |
||
897 | if (alt == CQ('O') && *alt_digits) |
||
898 | { |
||
899 | if (tim_p->tm_mday < 10) |
||
900 | { |
||
901 | if (*format == CQ('d')) |
||
902 | { |
||
903 | if (maxsize - count < 2) return 0; |
||
904 | len = conv_to_alt_digits (&s[count], maxsize - count, |
||
905 | 0, *alt_digits); |
||
906 | CHECK_LENGTH (); |
||
907 | } |
||
908 | if (*format == CQ('e') || len == 0) |
||
909 | s[count++] = CQ(' '); |
||
910 | } |
||
911 | len = conv_to_alt_digits (&s[count], maxsize - count, |
||
912 | tim_p->tm_mday, *alt_digits); |
||
913 | CHECK_LENGTH (); |
||
914 | if (len > 0) |
||
915 | break; |
||
916 | } |
||
917 | #endif /* _WANT_C99_TIME_FORMATS */ |
||
918 | len = snprintf (&s[count], maxsize - count, |
||
919 | *format == CQ('d') ? CQ("%.2d") : CQ("%2d"), |
||
920 | tim_p->tm_mday); |
||
921 | CHECK_LENGTH (); |
||
922 | break; |
||
923 | case CQ('D'): |
||
924 | /* %m/%d/%y */ |
||
925 | len = snprintf (&s[count], maxsize - count, |
||
926 | CQ("%.2d/%.2d/%.2d"), |
||
927 | tim_p->tm_mon + 1, tim_p->tm_mday, |
||
928 | tim_p->tm_year >= 0 ? tim_p->tm_year % 100 |
||
929 | : abs (tim_p->tm_year + YEAR_BASE) % 100); |
||
930 | CHECK_LENGTH (); |
||
931 | break; |
||
932 | case CQ('F'): |
||
933 | { /* %F is equivalent to "%+4Y-%m-%d", flags and width can change |
||
934 | that. Recurse to avoid need to replicate %Y formation. */ |
||
935 | CHAR fmtbuf[32], *fmt = fmtbuf; |
||
936 | |||
937 | *fmt++ = CQ('%'); |
||
938 | if (pad) /* '0' or '+' */ |
||
939 | *fmt++ = pad; |
||
940 | else |
||
941 | *fmt++ = '+'; |
||
942 | if (!pad) |
||
943 | width = 10; |
||
944 | if (width < 6) |
||
945 | width = 6; |
||
946 | width -= 6; |
||
947 | if (width) |
||
948 | { |
||
949 | len = snprintf (fmt, fmtbuf + 32 - fmt, CQ("%lu"), width); |
||
950 | if (len > 0) |
||
951 | fmt += len; |
||
952 | } |
||
953 | STRCPY (fmt, CQ("Y-%m-%d")); |
||
954 | len = __strftime (&s[count], maxsize - count, fmtbuf, tim_p, |
||
955 | era_info, alt_digits); |
||
956 | if (len > 0) |
||
957 | count += len; |
||
958 | else |
||
959 | return 0; |
||
960 | } |
||
961 | break; |
||
962 | case CQ('g'): |
||
963 | /* Be careful of both overflow and negative years, thanks to |
||
964 | the asymmetric range of years. */ |
||
965 | { |
||
966 | int adjust = iso_year_adjust (tim_p); |
||
967 | int year = tim_p->tm_year >= 0 ? tim_p->tm_year % 100 |
||
968 | : abs (tim_p->tm_year + YEAR_BASE) % 100; |
||
969 | if (adjust < 0 && tim_p->tm_year <= -YEAR_BASE) |
||
970 | adjust = 1; |
||
971 | else if (adjust > 0 && tim_p->tm_year < -YEAR_BASE) |
||
972 | adjust = -1; |
||
973 | len = snprintf (&s[count], maxsize - count, CQ("%.2d"), |
||
974 | ((year + adjust) % 100 + 100) % 100); |
||
975 | CHECK_LENGTH (); |
||
976 | } |
||
977 | break; |
||
978 | case CQ('G'): |
||
979 | { |
||
980 | /* See the comments for 'C' and 'Y'; this is a variable length |
||
981 | field. Although there is no requirement for a minimum number |
||
982 | of digits, we use 4 for consistency with 'Y'. */ |
||
983 | int sign = tim_p->tm_year < -YEAR_BASE; |
||
984 | int adjust = iso_year_adjust (tim_p); |
||
985 | int century = tim_p->tm_year >= 0 |
||
986 | ? tim_p->tm_year / 100 + YEAR_BASE / 100 |
||
987 | : abs (tim_p->tm_year + YEAR_BASE) / 100; |
||
988 | int year = tim_p->tm_year >= 0 ? tim_p->tm_year % 100 |
||
989 | : abs (tim_p->tm_year + YEAR_BASE) % 100; |
||
990 | if (adjust < 0 && tim_p->tm_year <= -YEAR_BASE) |
||
991 | sign = adjust = 1; |
||
992 | else if (adjust > 0 && sign) |
||
993 | adjust = -1; |
||
994 | year += adjust; |
||
995 | if (year == -1) |
||
996 | { |
||
997 | year = 99; |
||
998 | --century; |
||
999 | } |
||
1000 | else if (year == 100) |
||
1001 | { |
||
1002 | year = 0; |
||
1003 | ++century; |
||
1004 | } |
||
1005 | CHAR fmtbuf[10], *fmt = fmtbuf; |
||
1006 | /* int potentially overflows, so use unsigned instead. */ |
||
1007 | unsigned p_year = century * 100 + year; |
||
1008 | if (sign) |
||
1009 | *fmt++ = CQ('-'); |
||
1010 | else if (pad == CQ('+') && p_year >= 10000) |
||
1011 | { |
||
1012 | *fmt++ = CQ('+'); |
||
1013 | sign = 1; |
||
1014 | } |
||
1015 | if (width && sign) |
||
1016 | --width; |
||
1017 | *fmt++ = CQ('%'); |
||
1018 | if (pad) |
||
1019 | *fmt++ = CQ('0'); |
||
1020 | STRCPY (fmt, CQ(".*u")); |
||
1021 | len = snprintf (&s[count], maxsize - count, fmtbuf, width, p_year); |
||
1022 | if (len < 0 || (count+=len) >= maxsize) |
||
1023 | return 0; |
||
1024 | } |
||
1025 | break; |
||
1026 | case CQ('H'): |
||
1027 | #ifdef _WANT_C99_TIME_FORMATS |
||
1028 | if (alt == CQ('O') && *alt_digits) |
||
1029 | { |
||
1030 | len = conv_to_alt_digits (&s[count], maxsize - count, |
||
1031 | tim_p->tm_hour, *alt_digits); |
||
1032 | CHECK_LENGTH (); |
||
1033 | if (len > 0) |
||
1034 | break; |
||
1035 | } |
||
1036 | #endif /* _WANT_C99_TIME_FORMATS */ |
||
1037 | /*FALLTHRU*/ |
||
1038 | case CQ('k'): /* newlib extension */ |
||
1039 | len = snprintf (&s[count], maxsize - count, |
||
1040 | *format == CQ('k') ? CQ("%2d") : CQ("%.2d"), |
||
1041 | tim_p->tm_hour); |
||
1042 | CHECK_LENGTH (); |
||
1043 | break; |
||
1044 | case CQ('l'): /* newlib extension */ |
||
1045 | if (alt == CQ('O')) |
||
1046 | alt = CQ('\0'); |
||
1047 | /*FALLTHRU*/ |
||
1048 | case CQ('I'): |
||
1049 | { |
||
1050 | register int h12; |
||
1051 | h12 = (tim_p->tm_hour == 0 || tim_p->tm_hour == 12) ? |
||
1052 | 12 : tim_p->tm_hour % 12; |
||
1053 | #ifdef _WANT_C99_TIME_FORMATS |
||
1054 | if (alt != CQ('O') || !*alt_digits |
||
1055 | || !(len = conv_to_alt_digits (&s[count], maxsize - count, |
||
1056 | h12, *alt_digits))) |
||
1057 | #endif /* _WANT_C99_TIME_FORMATS */ |
||
1058 | len = snprintf (&s[count], maxsize - count, |
||
1059 | *format == CQ('I') ? CQ("%.2d") : CQ("%2d"), h12); |
||
1060 | CHECK_LENGTH (); |
||
1061 | } |
||
1062 | break; |
||
1063 | case CQ('j'): |
||
1064 | len = snprintf (&s[count], maxsize - count, CQ("%.3d"), |
||
1065 | tim_p->tm_yday + 1); |
||
1066 | CHECK_LENGTH (); |
||
1067 | break; |
||
1068 | case CQ('m'): |
||
1069 | #ifdef _WANT_C99_TIME_FORMATS |
||
1070 | if (alt != CQ('O') || !*alt_digits |
||
1071 | || !(len = conv_to_alt_digits (&s[count], maxsize - count, |
||
1072 | tim_p->tm_mon + 1, *alt_digits))) |
||
1073 | #endif /* _WANT_C99_TIME_FORMATS */ |
||
1074 | len = snprintf (&s[count], maxsize - count, CQ("%.2d"), |
||
1075 | tim_p->tm_mon + 1); |
||
1076 | CHECK_LENGTH (); |
||
1077 | break; |
||
1078 | case CQ('M'): |
||
1079 | #ifdef _WANT_C99_TIME_FORMATS |
||
1080 | if (alt != CQ('O') || !*alt_digits |
||
1081 | || !(len = conv_to_alt_digits (&s[count], maxsize - count, |
||
1082 | tim_p->tm_min, *alt_digits))) |
||
1083 | #endif /* _WANT_C99_TIME_FORMATS */ |
||
1084 | len = snprintf (&s[count], maxsize - count, CQ("%.2d"), |
||
1085 | tim_p->tm_min); |
||
1086 | CHECK_LENGTH (); |
||
1087 | break; |
||
1088 | case CQ('n'): |
||
1089 | if (count < maxsize - 1) |
||
1090 | s[count++] = CQ('\n'); |
||
1091 | else |
||
1092 | return 0; |
||
1093 | break; |
||
1094 | case CQ('p'): |
||
1095 | case CQ('P'): |
||
1096 | _ctloc (am_pm[tim_p->tm_hour < 12 ? 0 : 1]); |
||
1097 | for (i = 0; i < ctloclen; i++) |
||
1098 | { |
||
1099 | if (count < maxsize - 1) |
||
1100 | s[count++] = (*format == CQ('P') ? TOLOWER (ctloc[i]) |
||
1101 | : ctloc[i]); |
||
1102 | else |
||
1103 | return 0; |
||
1104 | } |
||
1105 | break; |
||
1106 | case CQ('R'): |
||
1107 | len = snprintf (&s[count], maxsize - count, CQ("%.2d:%.2d"), |
||
1108 | tim_p->tm_hour, tim_p->tm_min); |
||
1109 | CHECK_LENGTH (); |
||
1110 | break; |
||
1111 | case CQ('S'): |
||
1112 | #ifdef _WANT_C99_TIME_FORMATS |
||
1113 | if (alt != CQ('O') || !*alt_digits |
||
1114 | || !(len = conv_to_alt_digits (&s[count], maxsize - count, |
||
1115 | tim_p->tm_sec, *alt_digits))) |
||
1116 | #endif /* _WANT_C99_TIME_FORMATS */ |
||
1117 | len = snprintf (&s[count], maxsize - count, CQ("%.2d"), |
||
1118 | tim_p->tm_sec); |
||
1119 | CHECK_LENGTH (); |
||
1120 | break; |
||
1121 | case CQ('t'): |
||
1122 | if (count < maxsize - 1) |
||
1123 | s[count++] = CQ('\t'); |
||
1124 | else |
||
1125 | return 0; |
||
1126 | break; |
||
1127 | case CQ('T'): |
||
1128 | len = snprintf (&s[count], maxsize - count, CQ("%.2d:%.2d:%.2d"), |
||
1129 | tim_p->tm_hour, tim_p->tm_min, tim_p->tm_sec); |
||
1130 | CHECK_LENGTH (); |
||
1131 | break; |
||
1132 | case CQ('u'): |
||
1133 | #ifdef _WANT_C99_TIME_FORMATS |
||
1134 | if (alt == CQ('O') && *alt_digits) |
||
1135 | { |
||
1136 | len = conv_to_alt_digits (&s[count], maxsize - count, |
||
1137 | tim_p->tm_wday == 0 ? 7 |
||
1138 | : tim_p->tm_wday, |
||
1139 | *alt_digits); |
||
1140 | CHECK_LENGTH (); |
||
1141 | if (len > 0) |
||
1142 | break; |
||
1143 | } |
||
1144 | #endif /* _WANT_C99_TIME_FORMATS */ |
||
1145 | if (count < maxsize - 1) |
||
1146 | { |
||
1147 | if (tim_p->tm_wday == 0) |
||
1148 | s[count++] = CQ('7'); |
||
1149 | else |
||
1150 | s[count++] = CQ('0') + tim_p->tm_wday; |
||
1151 | } |
||
1152 | else |
||
1153 | return 0; |
||
1154 | break; |
||
1155 | case CQ('U'): |
||
1156 | #ifdef _WANT_C99_TIME_FORMATS |
||
1157 | if (alt != CQ('O') || !*alt_digits |
||
1158 | || !(len = conv_to_alt_digits (&s[count], maxsize - count, |
||
1159 | (tim_p->tm_yday + 7 - |
||
1160 | tim_p->tm_wday) / 7, |
||
1161 | *alt_digits))) |
||
1162 | #endif /* _WANT_C99_TIME_FORMATS */ |
||
1163 | len = snprintf (&s[count], maxsize - count, CQ("%.2d"), |
||
1164 | (tim_p->tm_yday + 7 - |
||
1165 | tim_p->tm_wday) / 7); |
||
1166 | CHECK_LENGTH (); |
||
1167 | break; |
||
1168 | case CQ('V'): |
||
1169 | { |
||
1170 | int adjust = iso_year_adjust (tim_p); |
||
1171 | int wday = (tim_p->tm_wday) ? tim_p->tm_wday - 1 : 6; |
||
1172 | int week = (tim_p->tm_yday + 10 - wday) / 7; |
||
1173 | if (adjust > 0) |
||
1174 | week = 1; |
||
1175 | else if (adjust < 0) |
||
1176 | /* Previous year has 53 weeks if current year starts on |
||
1177 | Fri, and also if current year starts on Sat and |
||
1178 | previous year was leap year. */ |
||
1179 | week = 52 + (4 >= (wday - tim_p->tm_yday |
||
1180 | - isleap (tim_p->tm_year |
||
1181 | + (YEAR_BASE - 1 |
||
1182 | - (tim_p->tm_year < 0 |
||
1183 | ? 0 : 2000))))); |
||
1184 | #ifdef _WANT_C99_TIME_FORMATS |
||
1185 | if (alt != CQ('O') || !*alt_digits |
||
1186 | || !(len = conv_to_alt_digits (&s[count], maxsize - count, |
||
1187 | week, *alt_digits))) |
||
1188 | #endif /* _WANT_C99_TIME_FORMATS */ |
||
1189 | len = snprintf (&s[count], maxsize - count, CQ("%.2d"), week); |
||
1190 | CHECK_LENGTH (); |
||
1191 | } |
||
1192 | break; |
||
1193 | case CQ('w'): |
||
1194 | #ifdef _WANT_C99_TIME_FORMATS |
||
1195 | if (alt == CQ('O') && *alt_digits) |
||
1196 | { |
||
1197 | len = conv_to_alt_digits (&s[count], maxsize - count, |
||
1198 | tim_p->tm_wday, *alt_digits); |
||
1199 | CHECK_LENGTH (); |
||
1200 | if (len > 0) |
||
1201 | break; |
||
1202 | } |
||
1203 | #endif /* _WANT_C99_TIME_FORMATS */ |
||
1204 | if (count < maxsize - 1) |
||
1205 | s[count++] = CQ('0') + tim_p->tm_wday; |
||
1206 | else |
||
1207 | return 0; |
||
1208 | break; |
||
1209 | case CQ('W'): |
||
1210 | { |
||
1211 | int wday = (tim_p->tm_wday) ? tim_p->tm_wday - 1 : 6; |
||
1212 | wday = (tim_p->tm_yday + 7 - wday) / 7; |
||
1213 | #ifdef _WANT_C99_TIME_FORMATS |
||
1214 | if (alt != CQ('O') || !*alt_digits |
||
1215 | || !(len = conv_to_alt_digits (&s[count], maxsize - count, |
||
1216 | wday, *alt_digits))) |
||
1217 | #endif /* _WANT_C99_TIME_FORMATS */ |
||
1218 | len = snprintf (&s[count], maxsize - count, CQ("%.2d"), wday); |
||
1219 | CHECK_LENGTH (); |
||
1220 | } |
||
1221 | break; |
||
1222 | case CQ('y'): |
||
1223 | { |
||
1224 | #ifdef _WANT_C99_TIME_FORMATS |
||
1225 | if (alt == 'E' && *era_info) |
||
1226 | len = snprintf (&s[count], maxsize - count, CQ("%d"), |
||
1227 | (*era_info)->year); |
||
1228 | else |
||
1229 | #endif /* _WANT_C99_TIME_FORMATS */ |
||
1230 | { |
||
1231 | /* Be careful of both overflow and negative years, thanks to |
||
1232 | the asymmetric range of years. */ |
||
1233 | int year = tim_p->tm_year >= 0 ? tim_p->tm_year % 100 |
||
1234 | : abs (tim_p->tm_year + YEAR_BASE) % 100; |
||
1235 | #ifdef _WANT_C99_TIME_FORMATS |
||
1236 | if (alt != CQ('O') || !*alt_digits |
||
1237 | || !(len = conv_to_alt_digits (&s[count], maxsize - count, |
||
1238 | year, *alt_digits))) |
||
1239 | #endif /* _WANT_C99_TIME_FORMATS */ |
||
1240 | len = snprintf (&s[count], maxsize - count, CQ("%.2d"), |
||
1241 | year); |
||
1242 | } |
||
1243 | CHECK_LENGTH (); |
||
1244 | } |
||
1245 | break; |
||
1246 | case CQ('Y'): |
||
1247 | #ifdef _WANT_C99_TIME_FORMATS |
||
1248 | if (alt == 'E' && *era_info) |
||
1249 | { |
||
1250 | ctloc = (*era_info)->era_Y; |
||
1251 | goto recurse; |
||
1252 | } |
||
1253 | else |
||
1254 | #endif /* _WANT_C99_TIME_FORMATS */ |
||
1255 | { |
||
1256 | CHAR fmtbuf[10], *fmt = fmtbuf; |
||
1257 | int sign = tim_p->tm_year < -YEAR_BASE; |
||
1258 | /* int potentially overflows, so use unsigned instead. */ |
||
1259 | register unsigned year = (unsigned) tim_p->tm_year |
||
1260 | + (unsigned) YEAR_BASE; |
||
1261 | if (sign) |
||
1262 | { |
||
1263 | *fmt++ = CQ('-'); |
||
1264 | year = UINT_MAX - year + 1; |
||
1265 | } |
||
1266 | else if (pad == CQ('+') && year >= 10000) |
||
1267 | { |
||
1268 | *fmt++ = CQ('+'); |
||
1269 | sign = 1; |
||
1270 | } |
||
1271 | if (width && sign) |
||
1272 | --width; |
||
1273 | *fmt++ = CQ('%'); |
||
1274 | if (pad) |
||
1275 | *fmt++ = CQ('0'); |
||
1276 | STRCPY (fmt, CQ(".*u")); |
||
1277 | len = snprintf (&s[count], maxsize - count, fmtbuf, width, |
||
1278 | year); |
||
1279 | CHECK_LENGTH (); |
||
1280 | } |
||
1281 | break; |
||
1282 | case CQ('z'): |
||
1283 | if (tim_p->tm_isdst >= 0) |
||
1284 | { |
||
1285 | long offset; |
||
1286 | __tzinfo_type *tz = __gettzinfo (); |
||
1287 | TZ_LOCK; |
||
1288 | /* The sign of this is exactly opposite the envvar TZ. We |
||
1289 | could directly use the global _timezone for tm_isdst==0, |
||
1290 | but have to use __tzrule for daylight savings. */ |
||
1291 | offset = -tz->__tzrule[tim_p->tm_isdst > 0].offset; |
||
1292 | TZ_UNLOCK; |
||
1293 | len = snprintf (&s[count], maxsize - count, CQ("%+03ld%.2ld"), |
||
1294 | offset / SECSPERHOUR, |
||
1295 | labs (offset / SECSPERMIN) % 60L); |
||
1296 | CHECK_LENGTH (); |
||
1297 | } |
||
1298 | break; |
||
1299 | case CQ('Z'): |
||
1300 | if (tim_p->tm_isdst >= 0) |
||
1301 | { |
||
1302 | int size; |
||
1303 | TZ_LOCK; |
||
1304 | size = strlen(_tzname[tim_p->tm_isdst > 0]); |
||
1305 | for (i = 0; i < size; i++) |
||
1306 | { |
||
1307 | if (count < maxsize - 1) |
||
1308 | s[count++] = _tzname[tim_p->tm_isdst > 0][i]; |
||
1309 | else |
||
1310 | { |
||
1311 | TZ_UNLOCK; |
||
1312 | return 0; |
||
1313 | } |
||
1314 | } |
||
1315 | TZ_UNLOCK; |
||
1316 | } |
||
1317 | break; |
||
1318 | case CQ('%'): |
||
1319 | if (count < maxsize - 1) |
||
1320 | s[count++] = CQ('%'); |
||
1321 | else |
||
1322 | return 0; |
||
1323 | break; |
||
1324 | default: |
||
1325 | return 0; |
||
1326 | } |
||
1327 | if (*format) |
||
1328 | format++; |
||
1329 | else |
||
1330 | break; |
||
1331 | } |
||
1332 | if (maxsize) |
||
1333 | s[count] = CQ('\0'); |
||
1334 | |||
1335 | return count; |
||
1336 | } |
||
1337 | |||
1338 | /* The remainder of this file can serve as a regression test. Compile |
||
1339 | * with -D_REGRESSION_TEST. */ |
||
1340 | #if defined(_REGRESSION_TEST) /* [Test code: */ |
||
1341 | |||
1342 | /* This test code relies on ANSI C features, in particular on the ability |
||
1343 | * of adjacent strings to be pasted together into one string. */ |
||
1344 | |||
1345 | /* Test output buffer size (should be larger than all expected results) */ |
||
1346 | #define OUTSIZE 256 |
||
1347 | |||
1348 | struct test { |
||
1349 | CHAR *fmt; /* Testing format */ |
||
1350 | size_t max; /* Testing maxsize */ |
||
1351 | size_t ret; /* Expected return value */ |
||
1352 | CHAR *out; /* Expected output string */ |
||
1353 | }; |
||
1354 | struct list { |
||
1355 | const struct tm *tms; /* Time used for these vectors */ |
||
1356 | const struct test *vec; /* Test vectors */ |
||
1357 | int cnt; /* Number of vectors */ |
||
1358 | }; |
||
1359 | |||
1360 | const char TZ[]="TZ=EST5EDT"; |
||
1361 | |||
1362 | /* Define list of test inputs and expected outputs, for the given time zone |
||
1363 | * and time. */ |
||
1364 | const struct tm tm0 = { |
||
1365 | /* Tue Dec 30 10:53:47 EST 2008 (time_t=1230648827) */ |
||
1366 | .tm_sec = 47, |
||
1367 | .tm_min = 53, |
||
1368 | .tm_hour = 9, |
||
1369 | .tm_mday = 30, |
||
1370 | .tm_mon = 11, |
||
1371 | .tm_year = 108, |
||
1372 | .tm_wday = 2, |
||
1373 | .tm_yday = 364, |
||
1374 | .tm_isdst = 0 |
||
1375 | }; |
||
1376 | const struct test Vec0[] = { |
||
1377 | /* Testing fields one at a time, expecting to pass, using exact |
||
1378 | * allowed length as what is needed. */ |
||
1379 | /* Using tm0 for time: */ |
||
1380 | #define EXP(s) sizeof(s)/sizeof(CHAR)-1, s |
||
1381 | { CQ("%a"), 3+1, EXP(CQ("Tue")) }, |
||
1382 | { CQ("%A"), 7+1, EXP(CQ("Tuesday")) }, |
||
1383 | { CQ("%b"), 3+1, EXP(CQ("Dec")) }, |
||
1384 | { CQ("%B"), 8+1, EXP(CQ("December")) }, |
||
1385 | { CQ("%c"), 24+1, EXP(CQ("Tue Dec 30 09:53:47 2008")) }, |
||
1386 | { CQ("%C"), 2+1, EXP(CQ("20")) }, |
||
1387 | { CQ("%d"), 2+1, EXP(CQ("30")) }, |
||
1388 | { CQ("%D"), 8+1, EXP(CQ("12/30/08")) }, |
||
1389 | { CQ("%e"), 2+1, EXP(CQ("30")) }, |
||
1390 | { CQ("%F"), 10+1, EXP(CQ("2008-12-30")) }, |
||
1391 | { CQ("%g"), 2+1, EXP(CQ("09")) }, |
||
1392 | { CQ("%G"), 4+1, EXP(CQ("2009")) }, |
||
1393 | { CQ("%h"), 3+1, EXP(CQ("Dec")) }, |
||
1394 | { CQ("%H"), 2+1, EXP(CQ("09")) }, |
||
1395 | { CQ("%I"), 2+1, EXP(CQ("09")) }, |
||
1396 | { CQ("%j"), 3+1, EXP(CQ("365")) }, |
||
1397 | { CQ("%k"), 2+1, EXP(CQ(" 9")) }, |
||
1398 | { CQ("%l"), 2+1, EXP(CQ(" 9")) }, |
||
1399 | { CQ("%m"), 2+1, EXP(CQ("12")) }, |
||
1400 | { CQ("%M"), 2+1, EXP(CQ("53")) }, |
||
1401 | { CQ("%n"), 1+1, EXP(CQ("\n")) }, |
||
1402 | { CQ("%p"), 2+1, EXP(CQ("AM")) }, |
||
1403 | { CQ("%r"), 11+1, EXP(CQ("09:53:47 AM")) }, |
||
1404 | { CQ("%R"), 5+1, EXP(CQ("09:53")) }, |
||
1405 | { CQ("%S"), 2+1, EXP(CQ("47")) }, |
||
1406 | { CQ("%t"), 1+1, EXP(CQ("\t")) }, |
||
1407 | { CQ("%T"), 8+1, EXP(CQ("09:53:47")) }, |
||
1408 | { CQ("%u"), 1+1, EXP(CQ("2")) }, |
||
1409 | { CQ("%U"), 2+1, EXP(CQ("52")) }, |
||
1410 | { CQ("%V"), 2+1, EXP(CQ("01")) }, |
||
1411 | { CQ("%w"), 1+1, EXP(CQ("2")) }, |
||
1412 | { CQ("%W"), 2+1, EXP(CQ("52")) }, |
||
1413 | { CQ("%x"), 8+1, EXP(CQ("12/30/08")) }, |
||
1414 | { CQ("%X"), 8+1, EXP(CQ("09:53:47")) }, |
||
1415 | { CQ("%y"), 2+1, EXP(CQ("08")) }, |
||
1416 | { CQ("%Y"), 4+1, EXP(CQ("2008")) }, |
||
1417 | { CQ("%z"), 5+1, EXP(CQ("-0500")) }, |
||
1418 | { CQ("%Z"), 3+1, EXP(CQ("EST")) }, |
||
1419 | { CQ("%%"), 1+1, EXP(CQ("%")) }, |
||
1420 | #undef EXP |
||
1421 | }; |
||
1422 | /* Define list of test inputs and expected outputs, for the given time zone |
||
1423 | * and time. */ |
||
1424 | const struct tm tm1 = { |
||
1425 | /* Wed Jul 2 23:01:13 EDT 2008 (time_t=1215054073) */ |
||
1426 | .tm_sec = 13, |
||
1427 | .tm_min = 1, |
||
1428 | .tm_hour = 23, |
||
1429 | .tm_mday = 2, |
||
1430 | .tm_mon = 6, |
||
1431 | .tm_year = 108, |
||
1432 | .tm_wday = 3, |
||
1433 | .tm_yday = 183, |
||
1434 | .tm_isdst = 1 |
||
1435 | }; |
||
1436 | const struct test Vec1[] = { |
||
1437 | /* Testing fields one at a time, expecting to pass, using exact |
||
1438 | * allowed length as what is needed. */ |
||
1439 | /* Using tm1 for time: */ |
||
1440 | #define EXP(s) sizeof(s)/sizeof(CHAR)-1, s |
||
1441 | { CQ("%a"), 3+1, EXP(CQ("Wed")) }, |
||
1442 | { CQ("%A"), 9+1, EXP(CQ("Wednesday")) }, |
||
1443 | { CQ("%b"), 3+1, EXP(CQ("Jul")) }, |
||
1444 | { CQ("%B"), 4+1, EXP(CQ("July")) }, |
||
1445 | { CQ("%c"), 24+1, EXP(CQ("Wed Jul 2 23:01:13 2008")) }, |
||
1446 | { CQ("%C"), 2+1, EXP(CQ("20")) }, |
||
1447 | { CQ("%d"), 2+1, EXP(CQ("02")) }, |
||
1448 | { CQ("%D"), 8+1, EXP(CQ("07/02/08")) }, |
||
1449 | { CQ("%e"), 2+1, EXP(CQ(" 2")) }, |
||
1450 | { CQ("%F"), 10+1, EXP(CQ("2008-07-02")) }, |
||
1451 | { CQ("%g"), 2+1, EXP(CQ("08")) }, |
||
1452 | { CQ("%G"), 4+1, EXP(CQ("2008")) }, |
||
1453 | { CQ("%h"), 3+1, EXP(CQ("Jul")) }, |
||
1454 | { CQ("%H"), 2+1, EXP(CQ("23")) }, |
||
1455 | { CQ("%I"), 2+1, EXP(CQ("11")) }, |
||
1456 | { CQ("%j"), 3+1, EXP(CQ("184")) }, |
||
1457 | { CQ("%k"), 2+1, EXP(CQ("23")) }, |
||
1458 | { CQ("%l"), 2+1, EXP(CQ("11")) }, |
||
1459 | { CQ("%m"), 2+1, EXP(CQ("07")) }, |
||
1460 | { CQ("%M"), 2+1, EXP(CQ("01")) }, |
||
1461 | { CQ("%n"), 1+1, EXP(CQ("\n")) }, |
||
1462 | { CQ("%p"), 2+1, EXP(CQ("PM")) }, |
||
1463 | { CQ("%r"), 11+1, EXP(CQ("11:01:13 PM")) }, |
||
1464 | { CQ("%R"), 5+1, EXP(CQ("23:01")) }, |
||
1465 | { CQ("%S"), 2+1, EXP(CQ("13")) }, |
||
1466 | { CQ("%t"), 1+1, EXP(CQ("\t")) }, |
||
1467 | { CQ("%T"), 8+1, EXP(CQ("23:01:13")) }, |
||
1468 | { CQ("%u"), 1+1, EXP(CQ("3")) }, |
||
1469 | { CQ("%U"), 2+1, EXP(CQ("26")) }, |
||
1470 | { CQ("%V"), 2+1, EXP(CQ("27")) }, |
||
1471 | { CQ("%w"), 1+1, EXP(CQ("3")) }, |
||
1472 | { CQ("%W"), 2+1, EXP(CQ("26")) }, |
||
1473 | { CQ("%x"), 8+1, EXP(CQ("07/02/08")) }, |
||
1474 | { CQ("%X"), 8+1, EXP(CQ("23:01:13")) }, |
||
1475 | { CQ("%y"), 2+1, EXP(CQ("08")) }, |
||
1476 | { CQ("%Y"), 4+1, EXP(CQ("2008")) }, |
||
1477 | { CQ("%z"), 5+1, EXP(CQ("-0400")) }, |
||
1478 | { CQ("%Z"), 3+1, EXP(CQ("EDT")) }, |
||
1479 | { CQ("%%"), 1+1, EXP(CQ("%")) }, |
||
1480 | #undef EXP |
||
1481 | #define VEC(s) s, sizeof(s)/sizeof(CHAR), sizeof(s)/sizeof(CHAR)-1, s |
||
1482 | #define EXP(s) sizeof(s)/sizeof(CHAR), sizeof(s)/sizeof(CHAR)-1, s |
||
1483 | { VEC(CQ("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz")) }, |
||
1484 | { CQ("0123456789%%%h:`~"), EXP(CQ("0123456789%Jul:`~")) }, |
||
1485 | { CQ("%R%h:`~ %x %w"), EXP(CQ("23:01Jul:`~ 07/02/08 3")) }, |
||
1486 | #undef VEC |
||
1487 | #undef EXP |
||
1488 | }; |
||
1489 | |||
1490 | #if YEAR_BASE == 1900 /* ( */ |
||
1491 | /* Checks for very large years. YEAR_BASE value relied upon so that the |
||
1492 | * answer strings can be predetermined. |
||
1493 | * Years more than 4 digits are not mentioned in the standard for %C, so the |
||
1494 | * test for those cases are based on the design intent (which is to print the |
||
1495 | * whole number, being the century). */ |
||
1496 | const struct tm tmyr0 = { |
||
1497 | /* Wed Jul 2 23:01:13 EDT [HUGE#] */ |
||
1498 | .tm_sec = 13, |
||
1499 | .tm_min = 1, |
||
1500 | .tm_hour = 23, |
||
1501 | .tm_mday = 2, |
||
1502 | .tm_mon = 6, |
||
1503 | .tm_year = INT_MAX - YEAR_BASE/2, |
||
1504 | .tm_wday = 3, |
||
1505 | .tm_yday = 183, |
||
1506 | .tm_isdst = 1 |
||
1507 | }; |
||
1508 | #if INT_MAX == 32767 |
||
1509 | # define YEAR CQ("33717") /* INT_MAX + YEAR_BASE/2 */ |
||
1510 | # define CENT CQ("337") |
||
1511 | # define Year CQ("17") |
||
1512 | # elif INT_MAX == 2147483647 |
||
1513 | # define YEAR CQ("2147484597") |
||
1514 | # define CENT CQ("21474845") |
||
1515 | # define Year CQ("97") |
||
1516 | # elif INT_MAX == 9223372036854775807 |
||
1517 | # define YEAR CQ("9223372036854776757") |
||
1518 | # define CENT CQ("92233720368547777") |
||
1519 | # define Year CQ("57") |
||
1520 | # else |
||
1521 | # error "Unrecognized INT_MAX value: enhance me to recognize what you have" |
||
1522 | #endif |
||
1523 | const struct test Vecyr0[] = { |
||
1524 | /* Testing fields one at a time, expecting to pass, using a larger |
||
1525 | * allowed length than what is needed. */ |
||
1526 | /* Using tmyr0 for time: */ |
||
1527 | #define EXP(s) sizeof(s)/sizeof(CHAR)-1, s |
||
1528 | { CQ("%C"), OUTSIZE, EXP(CENT) }, |
||
1529 | { CQ("%c"), OUTSIZE, EXP(CQ("Wed Jul 2 23:01:13 ")YEAR) }, |
||
1530 | { CQ("%D"), OUTSIZE, EXP(CQ("07/02/")Year) }, |
||
1531 | { CQ("%F"), OUTSIZE, EXP(YEAR CQ("-07-02")) }, |
||
1532 | { CQ("%x"), OUTSIZE, EXP(CQ("07/02/")Year) }, |
||
1533 | { CQ("%y"), OUTSIZE, EXP(Year) }, |
||
1534 | { CQ("%Y"), OUTSIZE, EXP(YEAR) }, |
||
1535 | #undef EXP |
||
1536 | }; |
||
1537 | #undef YEAR |
||
1538 | #undef CENT |
||
1539 | #undef Year |
||
1540 | /* Checks for very large negative years. YEAR_BASE value relied upon so that |
||
1541 | * the answer strings can be predetermined. */ |
||
1542 | const struct tm tmyr1 = { |
||
1543 | /* Wed Jul 2 23:01:13 EDT [HUGE#] */ |
||
1544 | .tm_sec = 13, |
||
1545 | .tm_min = 1, |
||
1546 | .tm_hour = 23, |
||
1547 | .tm_mday = 2, |
||
1548 | .tm_mon = 6, |
||
1549 | .tm_year = INT_MIN, |
||
1550 | .tm_wday = 3, |
||
1551 | .tm_yday = 183, |
||
1552 | .tm_isdst = 1 |
||
1553 | }; |
||
1554 | #if INT_MAX == 32767 |
||
1555 | # define YEAR CQ("-30868") /* INT_MIN + YEAR_BASE */ |
||
1556 | # define CENT CQ("-308") |
||
1557 | # define Year CQ("68") |
||
1558 | # elif INT_MAX == 2147483647 |
||
1559 | # define YEAR CQ("-2147481748") |
||
1560 | # define CENT CQ("-21474817") |
||
1561 | # define Year CQ("48") |
||
1562 | # elif INT_MAX == 9223372036854775807 |
||
1563 | # define YEAR CQ("-9223372036854773908") |
||
1564 | # define CENT CQ("-92233720368547739") |
||
1565 | # define Year CQ("08") |
||
1566 | # else |
||
1567 | # error "Unrecognized INT_MAX value: enhance me to recognize what you have" |
||
1568 | #endif |
||
1569 | const struct test Vecyr1[] = { |
||
1570 | /* Testing fields one at a time, expecting to pass, using a larger |
||
1571 | * allowed length than what is needed. */ |
||
1572 | /* Using tmyr1 for time: */ |
||
1573 | #define EXP(s) sizeof(s)/sizeof(CHAR)-1, s |
||
1574 | { CQ("%C"), OUTSIZE, EXP(CENT) }, |
||
1575 | { CQ("%c"), OUTSIZE, EXP(CQ("Wed Jul 2 23:01:13 ")YEAR) }, |
||
1576 | { CQ("%D"), OUTSIZE, EXP(CQ("07/02/")Year) }, |
||
1577 | { CQ("%F"), OUTSIZE, EXP(YEAR CQ("-07-02")) }, |
||
1578 | { CQ("%x"), OUTSIZE, EXP(CQ("07/02/")Year) }, |
||
1579 | { CQ("%y"), OUTSIZE, EXP(Year) }, |
||
1580 | { CQ("%Y"), OUTSIZE, EXP(YEAR) }, |
||
1581 | #undef EXP |
||
1582 | }; |
||
1583 | #undef YEAR |
||
1584 | #undef CENT |
||
1585 | #undef Year |
||
1586 | #endif /* YEAR_BASE ) */ |
||
1587 | |||
1588 | /* Checks for years just over zero (also test for s=60). |
||
1589 | * Years less than 4 digits are not mentioned for %Y in the standard, so the |
||
1590 | * test for that case is based on the design intent. */ |
||
1591 | const struct tm tmyrzp = { |
||
1592 | /* Wed Jul 2 23:01:60 EDT 0007 */ |
||
1593 | .tm_sec = 60, |
||
1594 | .tm_min = 1, |
||
1595 | .tm_hour = 23, |
||
1596 | .tm_mday = 2, |
||
1597 | .tm_mon = 6, |
||
1598 | .tm_year = 7-YEAR_BASE, |
||
1599 | .tm_wday = 3, |
||
1600 | .tm_yday = 183, |
||
1601 | .tm_isdst = 1 |
||
1602 | }; |
||
1603 | #define YEAR CQ("0007") /* Design intent: %Y=%C%y */ |
||
1604 | #define CENT CQ("00") |
||
1605 | #define Year CQ("07") |
||
1606 | const struct test Vecyrzp[] = { |
||
1607 | /* Testing fields one at a time, expecting to pass, using a larger |
||
1608 | * allowed length than what is needed. */ |
||
1609 | /* Using tmyrzp for time: */ |
||
1610 | #define EXP(s) sizeof(s)/sizeof(CHAR)-1, s |
||
1611 | { CQ("%C"), OUTSIZE, EXP(CENT) }, |
||
1612 | { CQ("%c"), OUTSIZE, EXP(CQ("Wed Jul 2 23:01:60 ")YEAR) }, |
||
1613 | { CQ("%D"), OUTSIZE, EXP(CQ("07/02/")Year) }, |
||
1614 | { CQ("%F"), OUTSIZE, EXP(YEAR CQ("-07-02")) }, |
||
1615 | { CQ("%x"), OUTSIZE, EXP(CQ("07/02/")Year) }, |
||
1616 | { CQ("%y"), OUTSIZE, EXP(Year) }, |
||
1617 | { CQ("%Y"), OUTSIZE, EXP(YEAR) }, |
||
1618 | #undef EXP |
||
1619 | }; |
||
1620 | #undef YEAR |
||
1621 | #undef CENT |
||
1622 | #undef Year |
||
1623 | /* Checks for years just under zero. |
||
1624 | * Negative years are not handled by the standard, so the vectors here are |
||
1625 | * verifying the chosen implemtation. */ |
||
1626 | const struct tm tmyrzn = { |
||
1627 | /* Wed Jul 2 23:01:00 EDT -004 */ |
||
1628 | .tm_sec = 00, |
||
1629 | .tm_min = 1, |
||
1630 | .tm_hour = 23, |
||
1631 | .tm_mday = 2, |
||
1632 | .tm_mon = 6, |
||
1633 | .tm_year = -4-YEAR_BASE, |
||
1634 | .tm_wday = 3, |
||
1635 | .tm_yday = 183, |
||
1636 | .tm_isdst = 1 |
||
1637 | }; |
||
1638 | #define YEAR CQ("-004") |
||
1639 | #define CENT CQ("-0") |
||
1640 | #define Year CQ("04") |
||
1641 | const struct test Vecyrzn[] = { |
||
1642 | /* Testing fields one at a time, expecting to pass, using a larger |
||
1643 | * allowed length than what is needed. */ |
||
1644 | /* Using tmyrzn for time: */ |
||
1645 | #define EXP(s) sizeof(s)/sizeof(CHAR)-1, s |
||
1646 | { CQ("%C"), OUTSIZE, EXP(CENT) }, |
||
1647 | { CQ("%c"), OUTSIZE, EXP(CQ("Wed Jul 2 23:01:00 ")YEAR) }, |
||
1648 | { CQ("%D"), OUTSIZE, EXP(CQ("07/02/")Year) }, |
||
1649 | { CQ("%F"), OUTSIZE, EXP(YEAR CQ("-07-02")) }, |
||
1650 | { CQ("%x"), OUTSIZE, EXP(CQ("07/02/")Year) }, |
||
1651 | { CQ("%y"), OUTSIZE, EXP(Year) }, |
||
1652 | { CQ("%Y"), OUTSIZE, EXP(YEAR) }, |
||
1653 | #undef EXP |
||
1654 | }; |
||
1655 | #undef YEAR |
||
1656 | #undef CENT |
||
1657 | #undef Year |
||
1658 | |||
1659 | const struct list ListYr[] = { |
||
1660 | { &tmyrzp, Vecyrzp, sizeof(Vecyrzp)/sizeof(Vecyrzp[0]) }, |
||
1661 | { &tmyrzn, Vecyrzn, sizeof(Vecyrzn)/sizeof(Vecyrzn[0]) }, |
||
1662 | #if YEAR_BASE == 1900 |
||
1663 | { &tmyr0, Vecyr0, sizeof(Vecyr0)/sizeof(Vecyr0[0]) }, |
||
1664 | { &tmyr1, Vecyr1, sizeof(Vecyr1)/sizeof(Vecyr1[0]) }, |
||
1665 | #endif |
||
1666 | }; |
||
1667 | |||
1668 | |||
1669 | /* List of tests to be run */ |
||
1670 | const struct list List[] = { |
||
1671 | { &tm0, Vec0, sizeof(Vec0)/sizeof(Vec0[0]) }, |
||
1672 | { &tm1, Vec1, sizeof(Vec1)/sizeof(Vec1[0]) }, |
||
1673 | }; |
||
1674 | |||
1675 | #if defined(STUB_getenv_r) |
||
1676 | char * |
||
1677 | _getenv_r(struct _reent *p, const char *cp) { return getenv(cp); } |
||
1678 | #endif |
||
1679 | |||
1680 | int |
||
1681 | main(void) |
||
1682 | { |
||
1683 | int i, l, errr=0, erro=0, tot=0; |
||
1684 | const char *cp; |
||
1685 | CHAR out[OUTSIZE]; |
||
1686 | size_t ret; |
||
1687 | |||
1688 | /* Set timezone so that %z and %Z tests come out right */ |
||
1689 | cp = TZ; |
||
1690 | if((i=putenv(cp))) { |
||
1691 | printf( "putenv(%s) FAILED, ret %d\n", cp, i); |
||
1692 | return(-1); |
||
1693 | } |
||
1694 | if(strcmp(getenv("TZ"),strchr(TZ,'=')+1)) { |
||
1695 | printf( "TZ not set properly in environment\n"); |
||
1696 | return(-2); |
||
1697 | } |
||
1698 | tzset(); |
||
1699 | |||
1700 | #if defined(VERBOSE) |
||
1701 | printf("_timezone=%d, _daylight=%d, _tzname[0]=%s, _tzname[1]=%s\n", _timezone, _daylight, _tzname[0], _tzname[1]); |
||
1702 | { |
||
1703 | long offset; |
||
1704 | __tzinfo_type *tz = __gettzinfo (); |
||
1705 | /* The sign of this is exactly opposite the envvar TZ. We |
||
1706 | could directly use the global _timezone for tm_isdst==0, |
||
1707 | but have to use __tzrule for daylight savings. */ |
||
1708 | printf("tz->__tzrule[0].offset=%d, tz->__tzrule[1].offset=%d\n", tz->__tzrule[0].offset, tz->__tzrule[1].offset); |
||
1709 | } |
||
1710 | #endif |
||
1711 | |||
1712 | /* Run all of the exact-length tests as-given--results should match */ |
||
1713 | for(l=0; l |
||
1714 | const struct list *test = &List[l]; |
||
1715 | for(i=0; i |
||
1716 | tot++; /* Keep track of number of tests */ |
||
1717 | ret = strftime(out, test->vec[i].max, test->vec[i].fmt, test->tms); |
||
1718 | if(ret != test->vec[i].ret) { |
||
1719 | errr++; |
||
1720 | fprintf(stderr, |
||
1721 | "ERROR: return %d != %d expected for List[%d].vec[%d]\n", |
||
1722 | ret, test->vec[i].ret, l, i); |
||
1723 | } |
||
1724 | if(strncmp(out, test->vec[i].out, test->vec[i].max-1)) { |
||
1725 | erro++; |
||
1726 | fprintf(stderr, |
||
1727 | "ERROR: \"%"SFLG"s\" != \"%"SFLG"s\" expected for List[%d].vec[%d]\n", |
||
1728 | out, test->vec[i].out, l, i); |
||
1729 | } |
||
1730 | } |
||
1731 | } |
||
1732 | |||
1733 | /* Run all of the exact-length tests with the length made too short--expect to |
||
1734 | * fail. */ |
||
1735 | for(l=0; l |
||
1736 | const struct list *test = &List[l]; |
||
1737 | for(i=0; i |
||
1738 | tot++; /* Keep track of number of tests */ |
||
1739 | ret = strftime(out, test->vec[i].max-1, test->vec[i].fmt, test->tms); |
||
1740 | if(ret != 0) { |
||
1741 | errr++; |
||
1742 | fprintf(stderr, |
||
1743 | "ERROR: return %d != %d expected for List[%d].vec[%d]\n", |
||
1744 | ret, 0, l, i); |
||
1745 | } |
||
1746 | /* Almost every conversion puts out as many characters as possible, so |
||
1747 | * go ahead and test the output even though have failed. (The test |
||
1748 | * times chosen happen to not hit any of the cases that fail this, so it |
||
1749 | * works.) */ |
||
1750 | if(strncmp(out, test->vec[i].out, test->vec[i].max-1-1)) { |
||
1751 | erro++; |
||
1752 | fprintf(stderr, |
||
1753 | "ERROR: \"%"SFLG"s\" != \"%"SFLG"s\" expected for List[%d].vec[%d]\n", |
||
1754 | out, test->vec[i].out, l, i); |
||
1755 | } |
||
1756 | } |
||
1757 | } |
||
1758 | |||
1759 | /* Run all of the special year test cases */ |
||
1760 | for(l=0; l |
||
1761 | const struct list *test = &ListYr[l]; |
||
1762 | for(i=0; i |
||
1763 | tot++; /* Keep track of number of tests */ |
||
1764 | ret = strftime(out, test->vec[i].max, test->vec[i].fmt, test->tms); |
||
1765 | if(ret != test->vec[i].ret) { |
||
1766 | errr++; |
||
1767 | fprintf(stderr, |
||
1768 | "ERROR: return %d != %d expected for ListYr[%d].vec[%d]\n", |
||
1769 | ret, test->vec[i].ret, l, i); |
||
1770 | } |
||
1771 | if(strncmp(out, test->vec[i].out, test->vec[i].max-1)) { |
||
1772 | erro++; |
||
1773 | fprintf(stderr, |
||
1774 | "ERROR: \"%"SFLG"s\" != \"%"SFLG"s\" expected for ListYr[%d].vec[%d]\n", |
||
1775 | out, test->vec[i].out, l, i); |
||
1776 | } |
||
1777 | } |
||
1778 | } |
||
1779 | |||
1780 | #define STRIZE(f) #f |
||
1781 | #define NAME(f) STRIZE(f) |
||
1782 | printf(NAME(strftime) "() test "); |
||
1783 | if(errr || erro) printf("FAILED %d/%d of", errr, erro); |
||
1784 | else printf("passed"); |
||
1785 | printf(" %d test cases.\n", tot); |
||
1786 | |||
1787 | return(errr || erro); |
||
1788 | } |
||
1789 | #endif /* defined(_REGRESSION_TEST) ] */>>>>>>>>>>>>>>=>>>>=>>>>>>>>>>>>>>>=>>>>=>>>=>=>><>><>>>>> |