1 /***********************************************************************
2 * *
3 * This software is part of the ast package *
4 * Copyright (c) 1985-2010 AT&T Intellectual Property *
5 * and is licensed under the *
6 * Common Public License, Version 1.0 *
7 * by AT&T Intellectual Property *
8 * *
9 * A copy of the License is available at *
10 * http://www.opensource.org/licenses/cpl1.0.txt *
11 * (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) *
12 * *
13 * Information and Software Systems Research *
14 * AT&T Research *
15 * Florham Park NJ *
16 * *
17 * Glenn Fowler <gsf@research.att.com> *
18 * David Korn <dgk@research.att.com> *
19 * Phong Vo <kpv@research.att.com> *
20 * *
21 ***********************************************************************/
22 #pragma prototyped
23 /*
24 * Glenn Fowler
25 * AT&T Research
26 *
27 * Time_t conversion support
28 */
29
30 #include <tmx.h>
31 #include <ctype.h>
32
33 #define warped(t,n) ((t)<((n)-tmxsns(6L*30L*24L*60L*60L,0))||(t)>((n)+tmxsns(24L*60L*60L,0)))
34
35 /*
36 * format n with padding p into s
37 * return end of s
38 *
39 * p: <0 blank padding
40 * 0 no padding
41 * >0 0 padding
42 */
43
44 static char*
number(register char * s,register char * e,register long n,register int p,int w,int pad)45 number(register char* s, register char* e, register long n, register int p, int w, int pad)
46 {
47 char* b;
48
49 if (w)
50 {
51 if (p > 0 && (pad == 0 || pad == '0'))
52 while (w > p)
53 {
54 p++;
55 n *= 10;
56 }
57 else if (w > p)
58 p = w;
59 }
60 switch (pad)
61 {
62 case '-':
63 p = 0;
64 break;
65 case '_':
66 if (p > 0)
67 p = -p;
68 break;
69 case '0':
70 if (p < 0)
71 p = -p;
72 break;
73 }
74 b = s;
75 if (p > 0)
76 s += sfsprintf(s, e - s, "%0*lu", p, n);
77 else if (p < 0)
78 s += sfsprintf(s, e - s, "%*lu", -p, n);
79 else
80 s += sfsprintf(s, e - s, "%lu", n);
81 if (w && (s - b) > w)
82 *(s = b + w) = 0;
83 return s;
84 }
85
86 typedef struct Stack_s
87 {
88 char* format;
89 int delimiter;
90 } Stack_t;
91
92 /*
93 * format t into buf of length len
94 * end of buf is returned
95 */
96
97 char*
tmxfmt(char * buf,size_t len,const char * format,Time_t t)98 tmxfmt(char* buf, size_t len, const char* format, Time_t t)
99 {
100 register char* cp;
101 register char* ep;
102 register char* p;
103 register int n;
104 int c;
105 int i;
106 int flags;
107 int alt;
108 int pad;
109 int delimiter;
110 int width;
111 int prec;
112 int parts;
113 char* arg;
114 char* f;
115 const char* oformat;
116 Tm_t* tm;
117 Tm_zone_t* zp;
118 Time_t now;
119 Stack_t* sp;
120 Stack_t stack[8];
121 Tm_t ts;
122 char argbuf[256];
123 char fmt[32];
124
125 tmlocale();
126 tm = tmxtm(&ts, t, NiL);
127 if (!format || !*format)
128 format = tm_info.deformat;
129 oformat = format;
130 flags = tm_info.flags;
131 sp = &stack[0];
132 cp = buf;
133 ep = buf + len;
134 delimiter = 0;
135 for (;;)
136 {
137 if ((c = *format++) == delimiter)
138 {
139 delimiter = 0;
140 if (sp <= &stack[0])
141 break;
142 sp--;
143 format = sp->format;
144 delimiter = sp->delimiter;
145 continue;
146 }
147 if (c != '%')
148 {
149 if (cp < ep)
150 *cp++ = c;
151 continue;
152 }
153 alt = 0;
154 arg = 0;
155 pad = 0;
156 width = 0;
157 prec = 0;
158 parts = 0;
159 for (;;)
160 {
161 switch (c = *format++)
162 {
163 case '_':
164 case '-':
165 pad = c;
166 continue;
167 case 'E':
168 case 'O':
169 if (!isalpha(*format))
170 break;
171 alt = c;
172 continue;
173 case '0':
174 if (!parts)
175 {
176 pad = c;
177 continue;
178 }
179 /*FALLTHROUGH*/
180 case '1':
181 case '2':
182 case '3':
183 case '4':
184 case '5':
185 case '6':
186 case '7':
187 case '8':
188 case '9':
189 switch (parts)
190 {
191 case 0:
192 parts++;
193 /*FALLTHROUGH*/
194 case 1:
195 width = width * 10 + (c - '0');
196 break;
197 case 2:
198 prec = prec * 10 + (c - '0');
199 break;
200 }
201 continue;
202 case '.':
203 if (!parts++)
204 parts++;
205 continue;
206 case '(':
207 i = 1;
208 arg = argbuf;
209 for (;;)
210 {
211 if (!(c = *format++))
212 {
213 format--;
214 break;
215 }
216 else if (c == '(')
217 i++;
218 else if (c == ')' && !--i)
219 break;
220 else if (arg < &argbuf[sizeof(argbuf) - 1])
221 *arg++ = c;
222 }
223 *arg = 0;
224 arg = argbuf;
225 continue;
226 default:
227 break;
228 }
229 break;
230 }
231 switch (c)
232 {
233 case 0:
234 format--;
235 continue;
236 case '%':
237 if (cp < ep)
238 *cp++ = '%';
239 continue;
240 case '?':
241 if (tm_info.deformat != tm_info.format[TM_DEFAULT])
242 format = tm_info.deformat;
243 else if (!*format)
244 format = tm_info.format[TM_DEFAULT];
245 continue;
246 case 'a': /* abbreviated day of week name */
247 n = TM_DAY_ABBREV + tm->tm_wday;
248 goto index;
249 case 'A': /* day of week name */
250 n = TM_DAY + tm->tm_wday;
251 goto index;
252 case 'b': /* abbreviated month name */
253 case 'h':
254 n = TM_MONTH_ABBREV + tm->tm_mon;
255 goto index;
256 case 'B': /* month name */
257 n = TM_MONTH + tm->tm_mon;
258 goto index;
259 case 'c': /* `ctime(3)' date sans newline */
260 p = tm_info.format[TM_CTIME];
261 goto push;
262 case 'C': /* 2 digit century */
263 cp = number(cp, ep, (long)(1900 + tm->tm_year) / 100, 2, width, pad);
264 continue;
265 case 'd': /* day of month */
266 cp = number(cp, ep, (long)tm->tm_mday, 2, width, pad);
267 continue;
268 case 'D': /* date */
269 p = tm_info.format[TM_DATE];
270 goto push;
271 case 'E': /* OBSOLETE no pad day of month */
272 cp = number(cp, ep, (long)tm->tm_mday, 0, width, pad);
273 continue;
274 case 'e': /* blank padded day of month */
275 cp = number(cp, ep, (long)tm->tm_mday, -2, width, pad);
276 continue;
277 case 'f': /* TM_DEFAULT override */
278 p = tm_info.deformat;
279 goto push;
280 case 'F': /* ISO 8601:2000 standard date format */
281 p = "%Y-%m-%d";
282 goto push;
283 case 'g': /* %V 2 digit year */
284 case 'G': /* %V 4 digit year */
285 n = tm->tm_year + 1900;
286 if (tm->tm_yday < 7)
287 {
288 if (tmweek(tm, 2, -1, -1) >= 52)
289 n--;
290 }
291 else if (tm->tm_yday > 358)
292 {
293 if (tmweek(tm, 2, -1, -1) <= 1)
294 n++;
295 }
296 if (c == 'g')
297 {
298 n %= 100;
299 c = 2;
300 }
301 else
302 c = 4;
303 cp = number(cp, ep, (long)n, c, width, pad);
304 continue;
305 case 'H': /* hour (0 - 23) */
306 cp = number(cp, ep, (long)tm->tm_hour, 2, width, pad);
307 continue;
308 case 'i': /* international `date(1)' date */
309 p = tm_info.format[TM_INTERNATIONAL];
310 goto push;
311 case 'I': /* hour (0 - 12) */
312 if ((n = tm->tm_hour) > 12) n -= 12;
313 else if (n == 0) n = 12;
314 cp = number(cp, ep, (long)n, 2, width, pad);
315 continue;
316 case 'J': /* Julian date (0 offset) */
317 cp = number(cp, ep, (long)tm->tm_yday, 3, width, pad);
318 continue;
319 case 'j': /* Julian date (1 offset) */
320 cp = number(cp, ep, (long)(tm->tm_yday + 1), 3, width, pad);
321 continue;
322 case 'k': /* `date(1)' date */
323 p = tm_info.format[TM_DATE_1];
324 goto push;
325 case 'K':
326 switch (alt)
327 {
328 case 'E':
329 p = (pad == '_') ? "%Y-%m-%d %H:%M:%S.%N %z" : "%Y-%m-%d+%H:%M:%S.%N%z";
330 break;
331 case 'O':
332 p = (pad == '_') ? "%Y-%m-%d %H:%M:%S.%N" : "%Y-%m-%d+%H:%M:%S.%N";
333 break;
334 default:
335 p = (pad == '_') ? "%Y-%m-%d %H:%M:%S" : "%Y-%m-%d+%H:%M:%S";
336 break;
337 }
338 goto push;
339 case 'l': /* `ls -l' date */
340 if (t)
341 {
342 now = tmxgettime();
343 if (warped(t, now))
344 {
345 p = tm_info.format[TM_DISTANT];
346 goto push;
347 }
348 }
349 p = tm_info.format[TM_RECENT];
350 goto push;
351 case 'L': /* TM_DEFAULT */
352 case 'O': /* OBSOLETE */
353 p = tm_info.format[TM_DEFAULT];
354 goto push;
355 case 'm': /* month number */
356 cp = number(cp, ep, (long)(tm->tm_mon + 1), 2, width, pad);
357 continue;
358 case 'M': /* minutes */
359 cp = number(cp, ep, (long)tm->tm_min, 2, width, pad);
360 continue;
361 case 'n':
362 if (cp < ep)
363 *cp++ = '\n';
364 continue;
365 case 'N': /* nanosecond part */
366 cp = number(cp, ep, (long)tm->tm_nsec, 9, width, pad);
367 continue;
368 case 'o': /* set options */
369 if (arg)
370 goto options;
371 /*OBSOLETE*/
372 p = tm_info.deformat;
373 goto push;
374 case 'p': /* meridian */
375 n = TM_MERIDIAN + (tm->tm_hour >= 12);
376 goto index;
377 case 'q': /* time zone type (nation code) */
378 if (!(flags & TM_UTC))
379 {
380 if ((zp = tm->tm_zone) != tm_info.local)
381 for (; zp >= tm_data.zone; zp--)
382 if (p = zp->type)
383 goto string;
384 else if (p = zp->type)
385 goto string;
386 }
387 continue;
388 case 'Q': /* %Q<alpha> or %Q<delim>recent<delim>distant<delim> */
389 if (c = *format)
390 {
391 format++;
392 if (isalpha(c))
393 {
394 switch (c)
395 {
396 case 'd': /* `ls -l' distant date */
397 p = tm_info.format[TM_DISTANT];
398 goto push;
399 case 'r': /* `ls -l' recent date */
400 p = tm_info.format[TM_RECENT];
401 goto push;
402 default:
403 format--;
404 break;
405 }
406 }
407 else
408 {
409 if (t)
410 {
411 now = tmxgettime();
412 p = warped(t, now) ? (char*)0 : (char*)format;
413 }
414 else
415 p = (char*)format;
416 i = 0;
417 while (n = *format)
418 {
419 format++;
420 if (n == c)
421 {
422 if (!p)
423 p = (char*)format;
424 if (++i == 2)
425 goto push_delimiter;
426 }
427 }
428 }
429 }
430 continue;
431 case 'r':
432 p = tm_info.format[TM_MERIDIAN_TIME];
433 goto push;
434 case 'R':
435 p = "%H:%M";
436 goto push;
437 case 's': /* seconds[.nanoseconds] since the epoch */
438 case '#':
439 now = t;
440 f = fmt;
441 *f++ = '%';
442 if (pad == '0')
443 *f++ = pad;
444 if (width)
445 f += sfsprintf(f, &fmt[sizeof(fmt)] - f, "%d", width);
446 f += sfsprintf(f, &fmt[sizeof(fmt)] - f, "I%du", sizeof(Tmxsec_t));
447 cp += sfsprintf(cp, ep - cp, fmt, tmxsec(now));
448 if (parts > 1)
449 {
450 n = sfsprintf(cp, ep - cp, ".%09I*u", sizeof(Tmxnsec_t), tmxnsec(now));
451 if (prec && n >= prec)
452 n = prec + 1;
453 cp += n;
454 }
455 continue;
456 case 'S': /* seconds */
457 cp = number(cp, ep, (long)tm->tm_sec, 2, width, pad);
458 if ((flags & TM_SUBSECOND) && (format - 2) != oformat)
459 {
460 p = ".%N";
461 goto push;
462 }
463 continue;
464 case 't':
465 if (cp < ep)
466 *cp++ = '\t';
467 continue;
468 case 'T':
469 p = tm_info.format[TM_TIME];
470 goto push;
471 case 'u': /* weekday number [1(Monday)-7] */
472 if (!(i = tm->tm_wday))
473 i = 7;
474 cp = number(cp, ep, (long)i, 0, width, pad);
475 continue;
476 case 'U': /* week number, Sunday as first day */
477 cp = number(cp, ep, (long)tmweek(tm, 0, -1, -1), 2, width, pad);
478 continue;
479 case 'V': /* ISO week number */
480 cp = number(cp, ep, (long)tmweek(tm, 2, -1, -1), 2, width, pad);
481 continue;
482 case 'W': /* week number, Monday as first day */
483 cp = number(cp, ep, (long)tmweek(tm, 1, -1, -1), 2, width, pad);
484 continue;
485 case 'w': /* weekday number [0(Sunday)-6] */
486 cp = number(cp, ep, (long)tm->tm_wday, 0, width, pad);
487 continue;
488 case 'x':
489 p = tm_info.format[TM_DATE];
490 goto push;
491 case 'X':
492 p = tm_info.format[TM_TIME];
493 goto push;
494 case 'y': /* year in the form yy */
495 cp = number(cp, ep, (long)(tm->tm_year % 100), 2, width, pad);
496 continue;
497 case 'Y': /* year in the form ccyy */
498 cp = number(cp, ep, (long)(1900 + tm->tm_year), 4, width, pad);
499 continue;
500 case 'z': /* time zone west offset */
501 if (arg)
502 {
503 if ((zp = tmzone(arg, &f, 0, 0)) && !*f && tm->tm_zone != zp)
504 tm = tmxtm(tm, tmxtime(tm, tm->tm_zone->west + (tm->tm_isdst ? tm->tm_zone->dst : 0)), zp);
505 continue;
506 }
507 if ((ep - cp) >= 16)
508 cp = tmpoff(cp, ep - cp, "", (flags & TM_UTC) ? 0 : tm->tm_zone->west + (tm->tm_isdst ? tm->tm_zone->dst : 0), 24 * 60);
509 continue;
510 case 'Z': /* time zone */
511 if (arg)
512 {
513 if ((zp = tmzone(arg, &f, 0, 0)) && !*f && tm->tm_zone != zp)
514 tm = tmxtm(tm, tmxtime(tm, tm->tm_zone->west + (tm->tm_isdst ? tm->tm_zone->dst : 0)), zp);
515 continue;
516 }
517 p = (flags & TM_UTC) ? tm_info.format[TM_UT] : tm->tm_isdst && tm->tm_zone->daylight ? tm->tm_zone->daylight : tm->tm_zone->standard;
518 goto string;
519 case '+': /* old %+flag */
520 case '!': /* old %!flag */
521 format--;
522 /*FALLTHROUGH*/
523 case '=': /* old %=[=][+-]flag */
524 for (arg = argbuf; *format == '=' || *format == '-' || *format == '+' || *format == '!'; format++)
525 if (arg < &argbuf[sizeof(argbuf) - 2])
526 *arg++ = *format;
527 if (*arg++ = *format)
528 format++;
529 *arg = 0;
530 arg = argbuf;
531 goto options;
532 default:
533 if (cp < ep)
534 *cp++ = '%';
535 if (cp < ep)
536 *cp++ = c;
537 continue;
538 }
539 index:
540 p = tm_info.format[n];
541 string:
542 while (cp < ep && (*cp = *p++))
543 cp++;
544 continue;
545 options:
546 c = '+';
547 i = 0;
548 for (;;)
549 {
550 switch (*arg++)
551 {
552 case 0:
553 n = 0;
554 break;
555 case '=':
556 i = !i;
557 continue;
558 case '+':
559 case '-':
560 case '!':
561 c = *(arg - 1);
562 continue;
563 case 'l':
564 n = TM_LEAP;
565 break;
566 case 'n':
567 case 's':
568 n = TM_SUBSECOND;
569 break;
570 case 'u':
571 n = TM_UTC;
572 break;
573 default:
574 continue;
575 }
576 if (!n)
577 break;
578
579 /*
580 * right, the global state stinks
581 * but we respect its locale-like status
582 */
583
584 if (c == '+')
585 {
586 if (!(flags & n))
587 {
588 flags |= n;
589 tm_info.flags |= n;
590 tm = tmxtm(tm, t, (flags & TM_UTC) ? &tm_data.zone[2] : tm->tm_zone);
591 if (!i)
592 tm_info.flags &= ~n;
593 }
594 }
595 else if (flags & n)
596 {
597 flags &= ~n;
598 tm_info.flags &= ~n;
599 tm = tmxtm(tm, t, (flags & TM_UTC) ? &tm_data.zone[2] : tm->tm_zone);
600 if (!i)
601 tm_info.flags |= n;
602 }
603 }
604 continue;
605 push:
606 c = 0;
607 push_delimiter:
608 if (sp < &stack[elementsof(stack)])
609 {
610 sp->format = (char*)format;
611 format = p;
612 sp->delimiter = delimiter;
613 delimiter = c;
614 sp++;
615 }
616 continue;
617 }
618 tm_info.flags = flags;
619 if (cp >= ep)
620 cp = ep - 1;
621 *cp = 0;
622 return cp;
623 }
624